Я много читал о С++ Правиле трех. Многие клянутся этим. Но когда правило указано, оно почти всегда включает слово "обычно", "вероятно" или "возможно", указывая на наличие исключений. Я не видел много дискуссий о том, каковы могут быть эти исключительные случаи - случаи, когда правило трех не выполняется, или, по крайней мере, там, где соблюдение этого не дает никаких преимуществ.
Мой вопрос заключается в том, является ли моя ситуация законным исключением из правила трех.. Я считаю, что в описанной ниже ситуации необходимы явно определенный конструктор копирования и оператор назначения копирования, но default (неявно сгенерированный) деструктор будет работать нормально. Вот моя ситуация:
У меня есть два класса: A и B. Здесь речь идет о A. B является другом A. A содержит объект B. B содержит указатель A, который предназначен для указания на объект A, которому принадлежит объект B. B использует этот указатель для управления частными членами объекта A. B никогда не создается, кроме как в конструкторе A. Вот так:
// A.h
#include "B.h"
class A
{
private:
B b;
int x;
public:
friend class B;
A( int i = 0 )
: b( this ) {
x = i;
};
};
и...
// 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
Зачем мне создавать мои классы? Обещаю, у меня есть веские причины. Эти классы действительно делают больше, чем то, что я здесь включил.
Так что все остальное легко, не так ли? Нет управления ресурсами, нет Большой тройки, без проблем. Неправильно! Конструктор копии по умолчанию (неявный) для A не будет достаточным. Если мы это сделаем:
A a1;
A a2(a1);
мы получаем новый объект A a2
, который идентичен a1
, что означает, что a2.b
идентичен a1.b
, а это означает, что a2.b.ap
все еще указывает на a1
! Это не то, что мы хотим. Мы должны определить конструктор копирования для A, который дублирует функциональность конструктора копии по умолчанию, а затем устанавливает новый A::b.ap
для указания на новый объект A. Мы добавляем этот код к 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
};
Оператор присваивания копии необходим по той же причине и будет реализован с использованием того же процесса дублирования функциональных возможностей оператора присваивания копии по умолчанию, а затем вызова b.init( this );
.
Но нет необходимости в явном деструкторе; Эрго эта ситуация является исключением из правила трех. Я прав?