With B
the compiler can detect that the Rule of Three is potentially being violated and raises the Effective C warning. Resolving this is covered well in many other places starting with Ted Lyngmo's answer to this question.
But why don't the other two trigger the same warning?
C
allows us to eliminate half of the concern: The reference member variable cannot be re-assigned preventing the compiler from generating a default assignment operator to cause any grief.
C c; // uninitialized second. GCC misses this
C d;
c = d; //fails. deleted assignment operator
but the copy constructor should still be possible and a potential threat.
C c; // uninitialized second. GCC misses this
C d(c); // but it does catch the uninitialized second if you do this
Making a modification to C
std::vector<int> dummy;
struct C
{
C() :second(dummy) // initialize second
{
}
int * first = nullptr;
std::vector<int>& second;
};
Allows
C c;
C d(c);
to compile without the Effective C++ warning just like A
. I couldn't wrap my head around this for a long while. This brings up an important point. Warnings are given by the grace of the Implementor. If something is hard or impossible to prove, there will be no warning.
But why is this warning hard?
The compiler has to know to look for a potential problem. This means it will be looking for the signature of the problem. This means one or more members that may require special handling, a destructor, copy constructor, or assignment operator without at least one of the other two special member functions required by the Rule of Three.
I suspect GCC is triggering the Effective C++ warning when it finds a at least one of the special member functions but not all.
Let's look at the destructors of the three classes.
A
's int
requires no special destruction logic. Neither does a C
's reference. B
's vector
is a different story. At the very least it needs to release some storage. This requires the compiler to generate some code, and once there's a more than a do-nothing destructor the compiler can see that the class has a destructor without the other two parts of the Rule of Three and contains members that may require special handling (the pointer).
So
struct C
{
C() :second(dummy)
{
}
~C() // force a destructor
{
}
int * first = nullptr;
std::vector<int>& second;
};
Should, and does, raise the effective C++ warning.
Note: the utterly trivial copy constructor generated by
C c;
C d(c);
doesn't seem to set off the warning by itself. Nor does providing a copy constructor. The warning's hook may be solely on the destructor, leading back to the caveat about warnings existing only by the grace of the Implementor.