If you ever make a class or struct that has a member reference, there are important things you need to know.
Consider the following working code:
#include <vector>
class X {
public:
int val;
X(int val_param) : val(val_param) {}
};
int main() {
int i = 0, j = 1;
X x(i);
X y(j);
x = y;
return 0;
}
This works because the compiler automatically generates a member-wise copy assignment operator. That would look something like this:
X& operator=(const X& other) {
this->val = other.val;
return *this;
}
The copy assignment simply copies val
from other
into this->val
, which is valid.
The Problem with References
Now consider a small change where X
has a reference to an int
:
#include <vector>
class X {
public:
int& ref;
X(int& ref_param) : ref(ref_param) {}
};
int main() {
int i = 0, j = 1;
X x(i);
X y(j);
x = y;
return 0;
}
If the compiler were to generate a copy assignment operator for this version, it would look like this:
X& operator=(const X& other) {
this->ref = other.ref; // Error: References cannot be reassigned!
return *this;
}
This fails because references cannot be reassigned after they are initialized. The reference this->ref
is permanently bound to whatever int
it was initialized with and cannot be redirected to another int
.
Issues with std::vector
If you don't catch this issue early, more problems will arise, especially when using std::vector
. Consider this:
#include <vector>
class X {
public:
int& ref;
X(int& ref_param) : ref(ref_param) {}
};
int main() {
int i = 5;
X x(i);
std::vector<X> mcs;
mcs.push_back(x);
mcs.erase(mcs.begin());
return 0;
}
Here’s why this is a problem:
mcs.push_back(x)
attempts to copyx
into the vector. But sinceX
has a reference member, this can cause unexpected behavior, especially if the reference goes out of scope.mcs.erase(mcs.begin())
triggers a shift in elements, invoking the copy assignment operator, which we already established is ill-formed.
Solutions
1. Use Pointers Instead of References
#include <vector>
class X {
public:
int* ptr;
X(int& ref_param) : ptr(&ref_param) {}
X& operator=(const X& other) {
this->ptr = other.ptr; // Reassigning pointer is valid
return *this;
}
};
int main() {
int i = 5, j = 10;
X x(i);
X y(j);
x = y; // Now works, as we are just copying a pointer
std::vector<X> mcs;
mcs.push_back(x);
mcs.erase(mcs.begin()); // Now safe
return 0;
}
Now, ptr
can be reassigned, avoiding the reference issue. However, you must ensure that the pointed-to object remains valid.
2. Use std::reference_wrapper
#include <vector>
#include <functional>
class X {
public:
std::reference_wrapper<int> ref;
X(int& ref_param) : ref(ref_param) {}
};
int main() {
int i = 5, j = 10;
X x(i);
X y(j);
x = y; // Works because reference_wrapper can be reassigned
std::vector<X> mcs;
mcs.push_back(x);
mcs.erase(mcs.begin()); // Now safe
return 0;
}
This approach allows you to store references without encountering reassignment errors.
Conclusion
When using references in a class, be mindful of:
- The compiler-generated copy assignment operator will not work if your class contains a reference.
- Using references in
std::vector
can lead to subtle issues due to element shifting and reassignments. - Prefer using pointers or
std::reference_wrapper
when you need reassignable references.