Зависимые классы как другие члены класса
У меня есть класс B
, который требует создания экземпляра класса A
:
class B
{
B(A* a); // there is no default constructor
};
Теперь я хочу создать класс, содержащий B
как член, поэтому мне также нужно добавить A
в качестве члена и предоставить его конструктору B
:
class C
{
C() : a(), b(&a) {}
A a; // 1. initialized as a()
B b; // 2. initialized as b(&a) - OK
};
Но проблема в том, что если кто-то изредка изменяет порядок определения переменных в классе, он сломает
class C
{
C() : a(), b(&a) {}
B b; // 1. initialized as b(&a) while "a" uninitialized
A a; // too late...
};
Есть ли хороший способ решить эту проблему без изменения классов A
и B
? Спасибо.
Ответы
Ответ 1
Есть ли хороший способ разрешить это без изменения классов A и B?
Включите предупреждения компилятора; для gcc это -Wreorder (который включен в -Wall):
cc1plus: warnings being treated as errors
t.cpp: In constructor 'A::A()':
Line 3: warning: 'A::y' will be initialized after
Line 3: warning: 'int A::x'
Line 2: warning: when initialized here
В качестве альтернативы используйте инструмент, подобный lint-like, который обнаруживает это.
Но проблема в том, что если кто-то время от времени меняет порядок определения переменных в классе...
Зачем им это делать? Я подозреваю, что вы слишком много беспокоитесь о том, что может случиться. Тем не менее, вы можете оставить комментарий в классе:
A a; // Must be listed before member 'b'!
B b;
Не стоит недооценивать силу хорошо сделанных комментариев.:) Тогда позвольте тому, кто целенаправленно игнорирует их, чтобы получить то, что они заслуживают; вы все-таки используете С++.
Ответ 2
Для решения этой проблемы используйте хорошо известную идиому С++ под названием Base-from-Member.
Определите базовый класс как,
class C_Base
{
A a; //moved `A a` to the base class!
C_Base() : a() {}
};
class C : public C_Base
{
C() : b(&a) {}
B b; // 1. initialized as b(&a) while "a" uninitialized
//A a; // too late...
};
Теперь a
гарантированно инициализируется до b
.
Ответ 3
Храните b в файле unique_ptr и установите его в теле, а не в списке инициализаторов:
class C
{
C() :a() {
b = std::unique_ptr<B>(new B(&a));
}
A a;
std::unique_ptr<B> b;
};
Ответ 4
Один вариант заключается в том, чтобы явно не хранить A, а вместо этого использовать динамическое распределение для создания нового A для хранения в B:
class C {
public:
C() : b(new A) {
// handled in initialization list
}
private:
B b;
};
Так как это гарантирует, что A создается до B, это должно помешать этой проблеме когда-либо возникать.
Ответ 5
Проблема в том, что вы стреляете в ногу третьим примером. В С++ порядок членов переменных в классе/структуре имеет значение. Независимо от того, как вы решаете свою конкретную проблему, если вы передаете неинициализированные данные конструктору из-за плохого дизайна/макета класса, вы будете работать с унифицированными данными и, возможно, получить поведение undefined, в зависимости от типа кода в место.
Чтобы обратиться к вашему конкретному примеру, если B
действительно требует A
, а отношения - один к одному, почему бы не создать новый класс AB
, который имеет как объект A
, так и объект B
в правильном порядке и передать адрес A
на B
. То есть:
class AB
{
public:
AB():b_(&a_) {}
private:
A a_;
B b_;
};
теперь класс C
может избежать проблемы с упорядочением, используя AB
вместо A
и B
:
class C
{
public:
...
private:
AB ab_;
};
Как уже упоминалось, это, конечно, предполагает соотношение 1:1 между A
и B
. Если объект A
может использоваться несколькими объектами B
, все становится более сложным.
Ответ 6
Я не уверен, сколько у вас контроля над реализацией и структурой C, но нужно ли использовать сами объекты в классе C? Не могли бы вы переопределить класс для использования указателей вместо этого, а затем переместить их из списка инициализации, например.
class C
{
C()
{
a = new A;
b = new B(a);
}
~C() {delete a; delete b;}
A* a;
B* b;
};
Это позволяет избежать проблемы с порядком в объявлении, но дает новую проблему обеспечения правильного создания. Кроме того, если вы очень часто создаете A LOT C, список инициализации выполняется немного быстрее.