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
.