I've read a lot about the C++ Rule of Three. Many people swear by it. But when the rule is stated, it almost always includes a word like "usually," "likely," or "probably," indicating that there are exceptions. I haven't seen much discussion of what these exceptional cases might be -- cases where the Rule of Three does not hold, or at least where adhering to it doesn't offer any advantage.
My question is whether my situation is a legitimate exception to the Rule of Three. I believe that in the situation I describe below, an explicitly defined copy constructor and copy assignment operator are necessary, but the default (implicitly generated) destructor will work fine. Here is my situation:
I have two classes, A and B. The one in question here is A. B is a friend of A. A contains a B object. B contains an A pointer which is intended to point to the A object that owns the B object. B uses this pointer to manipulate private members of the A object. B is never instantiated except in the A constructor. Like this:
// A.h
#include "B.h"
class A
{
private:
B b;
int x;
public:
friend class B;
A( int i = 0 )
: b( this ) {
x = i;
};
};
and...
// B.h
#ifndef B_H // preprocessor escape to avoid infinite #include loop
#define B_H
class A; // forward declaration
class B
{
private:
A * ap;
int y;
public:
B( A * a_ptr = 0 ) {
ap = a_ptr;
y = 1;
};
void init( A * a_ptr ) {
ap = a_ptr;
};
void f();
// this method has to be defined below
// because members of A can't be accessed here
};
#include "A.h"
void B::f() {
ap->x += y;
y++;
}
#endif
Why would I set up my classes like that? I promise, I have good reasons. These classes actually do way more than what I've included here.
So the rest is easy, right? No resource management, no Big Three, no problem. Wrong! The default (implicit) copy constructor for A will not suffice. If we do this:
A a1;
A a2(a1);
we get a new A object a2
that is identical to a1
, meaning that a2.b
is identical to a1.b
, meaning that a2.b.ap
is still pointing to a1
! This is not what we want. We must define a copy constructor for A that duplicates the functionality of the default copy constructor and then sets the new A::b.ap
to point to the new A object. We add this code to class A
:
public:
A( const A & other )
{
// first we duplicate the functionality of a default copy constructor
x = other.x;
b = other.b;
// b.y has been copied over correctly
// b.ap has been copied over and therefore points to 'other'
b.init( this ); // this extra step is necessary
};
A copy assignment operator is necessary for the same reason and would be implemented using the same process of duplicating the functionality of the default copy assignment operator and then calling b.init( this );
.
But there is no need for an explicit destructor; ergo this situation is an exception to the Rule of Three. Am I right?
_B
is illegal since all underscores followed by a capital letter are reserved for the system. – Marlenmarlenaweak_ptr
takes care of ownership when you have circular references. How does it, or the article, or the rule of zero, help with the problem mentioned in this question (which is NOT a problem about ownership)? – Ambassador