It is a well-known fact that there is no built-in mechanism that prevents member fields that are references from being invalidated, even if they are const. (For more background see: https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/)
The easiest solution is to only use non-reference member fields, and I am sticking to this wherever suitable.
However, for the following reasons, it may not be a suitable solution sometimes:
a) Polymorphism: we need references if we do not know the exact type at compile time.
b) The class is supposed to modify a particular object, not a copy of it.
c) Copying may be expensive.
Whenever a reference is unavoidable, I suggest the following rule: always let the ctor take it by pointer, not by reference. Thus the caller knows better what to expect.
For increased flexibility, I came up with the following idea, that uses shared pointers. It allows the object to be passed-in by reference, then it is copied. It also allows the object to be passed-in by shared pointer, then it is in fact shared between the object and the caller. The following code demonstrates it. Classes derived from class A are used for the member field in class B. Two constructors for class B are provided: one takes a shared pointer and the other take something else and makes a shared pointer out of it.
class A {
public:
int count = 0;
virtual void id() = 0;
};
class A1 : public A {
public:
void id() {
++count;
std::cout << "A1" << std::endl;
}
};
class A2 : public A {
public:
void id() {
++count;
std::cout << "A2" << std::endl;
}
};
class B {
private:
std::shared_ptr<A> a;
public:
template <typename T> B(std::shared_ptr<T> const& arg_a) : a(arg_a) {}
template <typename T> B(T const& arg_a) : B(std::make_shared<T>(arg_a)) {}
void operator()() { a->id(); }
};
We can pass-in objects:
auto a1 = A1();
auto a2 = A2();
auto b1 = B(a1);
auto b2 = B(a2);
b1(); std::cout << a1.count << std::endl;
b2(); std::cout << a2.count << std::endl;
This prints:
A1
0
A2
0
The 0s are expected since a1 and a2 are copied by make_shared.
It works with temporaries:
auto b1 = B(A1());
auto b2 = B(A2());
b1();
b2();
Here we pretend that when declaring a1 and a2, we do not yet know of which type they will be:
std::shared_ptr<A> a1, a2;
a1 = std::make_shared<A1>(A1());
a2 = std::make_shared<A2>(A2());
auto b1 = B(a1);
auto b2 = B(a2);
b1(); std::cout << a1->count << std::endl;
b2(); std::cout << a2->count << std::endl;
This prints:
A1
1
A2
1
The 1s are expected since we are actually sharing a1 and a2 with the objects from class B.
The following works as well:
auto a1 = std::make_shared<A1>(A1());
auto a2 = std::make_shared<A2>(A2());
auto b1 = B(a1);
auto b2 = B(a2);
Any suggestions or objections?