зависимые классы, как и другие члены класса - PullRequest
10 голосов
/ 16 февраля 2011

У меня есть класс 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
};

Но проблема в том, что если кто-то иногда меняет порядок переменных definition в классе он сломается

class C
{
    C() : a(), b(&a) {}
    B b; // 1. initialized as b(&a) while "a" uninitialized
    A a; // too late...
};

Есть ли хороший способ решить эту проблему без изменения классов A и B?Спасибо.

Ответы [ 6 ]

7 голосов
/ 16 февраля 2011

Есть ли хороший способ решить эту проблему без изменения классов 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

В качестве альтернативы, используйте подобный ворсинке инструмент, который обнаруживает это.проблема заключается в том, что если кто-то иногда меняет порядок определения переменных в классе…

Зачем ему это делать?Я подозреваю, что вы слишком беспокоитесь о том, что может произойти .Тем не менее, вы можете оставить комментарий в классе:

A a;  // Must be listed before member 'b'!
B b;

Не стоит недооценивать силу удачно размещенных комментариев.:) Тогда позвольте тому, кто целенаправленно игнорирует их, получить то, что они заслуживают;В конце концов, вы используете C ++.

5 голосов
/ 16 февраля 2011

Используйте известную идиому C ++ под названием 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.

2 голосов
/ 16 февраля 2011

Сохраните b в unique_ptr и установите его в теле, а не в списке инициализатора:

class C
{
    C() :a() {
        b = std::unique_ptr<B>(new B(&a));
    }
    A a;
    std::unique_ptr<B> b;
};
0 голосов
/ 16 февраля 2011

Я не уверен, насколько вы контролируете реализацию и структуру C, но необходимо ли использовать сами объекты в классе C? Не могли бы вы переопределить класс, чтобы использовать вместо него указатели, а затем переместить их из списка инициализации, например,

class C
{
   C()
   {
     a = new A;
     b = new B(a);
   }
   ~C() {delete a; delete b;}

   A* a;
   B* b;
};

Это позволяет избежать проблемы порядка в объявлении, но дает вам новую проблему, гарантирующую, что они созданы правильно. Кроме того, если вы очень часто создаете A LOT из C, список инициализации будет немного быстрее.

0 голосов
/ 16 февраля 2011

Проблема в том, что вы стреляете себе в ногу в третьем примере. В C ++ порядок переменных-членов в классе / структуре имеет значение. Независимо от того, как вы решите свою конкретную проблему, если вы передадите неинициализированные данные в конструктор из-за плохого дизайна класса / компоновки членов, вы будете работать с унифицированными данными и, возможно, получите неопределенное поведение, в зависимости от вида используемого кода.

Если обратиться к вашему конкретному примеру, если 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, все становится сложнее.

0 голосов
/ 16 февраля 2011

Один из вариантов - не хранить A в явном виде, а вместо этого использовать динамическое распределение для создания нового A для хранения в B:

class C {
public:
       C() : b(new A) {
           // handled in initialization list
       }
private:
       B b;
};

, поскольку это гарантирует, что A будет создано доB, это должно предотвратить возникновение этой проблемы.

...