How to make a function that can return either a static or automatic storage objects without copy in C++?

  • A+
Category:Languages

Suppose I want to write something a function such that:

  • it returns an object
  • under certain circumstances, depending on a parameter of the function, the object has a fixed value that can be calculated only once to save time. So the natural choice is to make that object static.
  • otherwise, the function must generate the object on the fly

What is the best to write this function, with the requirements that:

  • no copy constructors are required upon call, only move, to prevent copying all the data of an expensive object
  • I don't want to use new raw pointers. If pointers are required, they must be smart and auto delete

The use case is the following:

  • certain values are very common, so I want to cache them
  • other values are very rare, so I want them not to be cached

The caller should not know which values are common, the interface should be transparent for both cases.

So far, the only clean implementation that I've managed is to use shared_ptr as shown below, but it feels like overkill. In particular, because it makes a heap allocation, where it feels that one is not really required. Is there a better approach?

#include <cassert> #include <iostream> #include <memory>  struct C {     int i;     static int count;     C(int i) : i(i) {         std::cout << "constr" << std::endl;         count++;     }     C(const C& c) : C(c.i) {         std::cout << "copy" << std::endl;     }     ~C() {         std::cout << "destr" << std::endl;         count--;     } }; int C::count = 0;  std::shared_ptr<C> func_reg_maybe_static(int i) {     static auto static_obj = std::make_shared<C>(0);     if (i == 0) {         return static_obj;     } else {         return std::make_shared<C>(i);     } }  int main() {     assert(C::count == 0);      {         auto c(func_reg_maybe_static(0));         assert(c->i == 0);         assert(C::count == 1);     }     assert(C::count == 1);      {         auto c(func_reg_maybe_static(0));         assert(c->i == 0);         assert(C::count == 1);     }     assert(C::count == 1);      {         auto c(func_reg_maybe_static(1));         assert(c->i == 1);         assert(C::count == 2);     }     assert(C::count == 1);      {         auto c(func_reg_maybe_static(2));         assert(c->i == 2);         assert(C::count == 2);     }     assert(C::count == 1); } 

Which I compile with GCC 6.4.0:

g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main.out func_ret_maybe_static.cpp 

and it produces the expected output (guaranteed by copy elision I believe):

constr constr destr constr destr destr 

I've added the static reference counter C::count just to check that objects are actually getting deleted as expected.

If I didn't have the static case, I would just do directly:

C func_reg_maybe_static(int i) {     return C(i); } 

and copy elision / move semantics would make everything efficient.

However, if I try something analogous as in:

C func_reg_maybe_static(int i) {     static C c(0);     return c; } 

then C++ smartly stops just moving C, and starts to copy it, to avoid corrupting the static.

 


I don't really understand the purpose of the static/automatic stuff. If all you're trying to do is avoid repeated constructions when the argument has been used before, why not just have a cache instead?

C& func_reg(const int i) {     static std::unordered_map<int, C> cache;     auto it = cache.find(i);     if (it == cache.end())       it = cache.emplace(i, C(i));      return it->second; } 

Don't always want the cache? Fine! Add this:

C func_reg_nocache(const int i) {     return C(i); } 

If I've misunderstood and you really need this thing where passing is_static == true gives you an entirely different object (one constructed with 0 rather than i) then just make a new function for that; it's doing something distinct.

C& func_reg() {     static C obj(0);     return obj; }  C func_reg(const int i) {     return C(i); } 

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: