Зависимость от проблемы конструктора производного класса - PullRequest
2 голосов
/ 12 января 2010

Я работаю над устаревшей платформой. Допустим, «A» - это базовый класс, а «B» - производный класс. Оба класса выполняют некоторую критическую инициализацию фреймворка. FWIW, он интенсивно использует библиотеку ACE .

У меня есть ситуация, когда; экземпляр 'B' создан. Но коэффициент «A» зависит от некоторой инициализации, которая может быть выполнена только из «B».

Как мы знаем, когда создается экземпляр 'B', ctor для 'A' вызывается раньше, чем 'B'. Механизм virtual не работает с ctors, использование static functions исключено (из-за static-initialization-order-fiasco ).

Я решил использовать шаблон CRTP следующим образом: -

template<class Derived>
class A {
public:
  A(){
    static_cast<Derived*>(this)->fun();
  }
};

class B : public A<B> {
public:
  B() : a(0) {
    a = 10;
  }
  void fun() { std::cout << "Init Function, Variable a = " << a << std::endl; }
private:
  int a;
};

Но члены класса, которые инициализируются в списке инициализаторов, имеют неопределенные значения, поскольку они еще не выполнены (например, «a» в приведенном выше случае). В моем случае существует ряд таких переменных инициализации на основе фреймворка.

Существуют ли какие-либо известные схемы, позволяющие справиться с этой ситуацией?

Заранее спасибо,


Обновление

Основываясь на идее, выдвинутой dribeas, я придумал временное решение этой проблемы (полноценный рефакторинг пока не подходит под мои сроки). Следующий код продемонстрирует то же самое: -

// move all A's dependent data in 'B' to a new class 'C'.
class C {
public:
   C() : a(10)
   {  }
   int getA() { return a; }
private:
   int a;

};

// enhance class A's ctor with a pointer to the newly split class
class A {
public:
   A(C* cptr)
   {
     std::cout << "O.K. B's Init Data From C:- " << cptr->getA() <<
std::endl;
   }

};

// now modify the actual derived class 'B' as follows
class B : public C, public A {
public:
   B()
     : A(static_cast<C*>(this))
   { }

}; 

Подробнее об этом см. эту ссылку на c.l.c ++. M. Есть хорошее общее решение, данное Константином Ознобихиным.

Ответы [ 5 ]

3 голосов
/ 12 января 2010

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

Я видел, как это делалось раньше, что доставляло разработчикам немало хлопот: расширить класс ACE_Task, чтобы обеспечить периодический поток, который можно расширить с помощью конкретной функциональности, и активировать поток из конструктора периодических потоков только для того, чтобы выяснить, что в то время как в тестирование и чаще всего это работало, но в некоторых ситуациях поток фактически начинался до инициализации самого производного объекта.

Наследование - это прочные отношения, которые следует использовать только при необходимости. Если вы посмотрите на библиотеку потоков потоков (только документы, не нужно вдаваться в подробности) или библиотеку POCO, вы увидите, что они разбили проблему на две части: классы потоков контролируют выполнение потоков и вызывают переданный метод для них в конструкции: управление потоком отделено от фактического кода, который будет выполняться, и тот факт, что код, который должен быть выполнен, получен в качестве аргумента для конструктора, гарантирует, что он был создан до вызова конструктора потока.

Может быть, вы могли бы использовать тот же подход в вашем собственном коде. Разделите функциональность на две части: независимо от того, что сейчас делает производный класс, его следует переместить за пределы иерархии (boost использует функторы, POCO использует интерфейсы, используйте то, что вам больше всего подходит). Без лучшего описания того, что вы пытаетесь сделать, я не могу вдаваться в подробности.

Еще одна вещь, которую вы можете попробовать (это хрупко, и я бы рекомендовал против) разбить класс B на класс C, который не зависит от A, и класс B, который наследует от обоих, сначала от C, а затем от A (с HUGE предупреждающие комментарии там). Это гарантирует, что C будет создан до A. Затем сделайте подобъект C аргументом A (через интерфейс или как шаблонный аргумент). Вероятно, это будет самый быстрый взлом, но не самый удачный. Как только вы захотите изменить код, просто сделайте это правильно.

2 голосов
/ 12 января 2010

Возможно Ленивая инициализация сделает это за вас. Сохраните флаг в A, независимо от того, инициализирован он или нет. Всякий раз, когда вы вызываете метод, проверьте флаг. если это false, инициализируйте A (ctor of B был тогда запущен) и установите флаг в true.

2 голосов
/ 12 января 2010

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

Решением может быть передача вспомогательного объекта из производного класса в конструктор базового класса.

1 голос
/ 12 января 2010

Это плохой дизайн, и, как уже говорилось, это UB. Пожалуйста, рассмотрите возможность перемещения таких зависимостей в какой-либо другой метод, скажем «initialize», и вызовите этот метод initialize из вашего конструктора производного класса (или в любом месте, прежде чем вам действительно потребуется инициализация данных базового класса)

0 голосов
/ 12 января 2010

Хм. Итак, если я правильно читаю, «A» является частью устаревшего кода, и вы чертовски уверены, что правильный ответ на какую-то проблему - использовать производный класс B.

Мне кажется, что простейшим решением может быть создание статической фабричной функции в функциональном (не ООП) стиле;

static B& B::makeNew(...);

Кроме того, что вы говорите, что потерпели фиаско в статическом порядке инициализации? Я не думаю, что вы с такой настройкой, так как инициализация не происходит.

Хорошо, если посмотреть на проблему более подробно, «C» необходимо выполнить некоторые настройки с помощью «B», что необходимо для «A», только «A» получает первые чары, потому что вы хотите иметь наследование. Итак ... ложное наследование, позволяющее вам контролировать порядок строительства ...?

class A
{
    B* pB;
public:
    rtype fakeVirtual(params) { return pB->fakeVirtual(params); }

    ~A()
    {
        pB->deleteFromA();
        delete pB;
        //Deletion stuff
    }

protected:
    void deleteFromB()
    {
        //Deletion stuff
        pB = NULL;
    }
}

class B
{
    A* pA;
public:
    rtype fakeInheritance(params) {return pA->fakeInheritance(params);}

    ~B()
    {
        //deletion stuff
        pA->deleteFromB();
    }

protected:
    friend class A;
    void deleteFromA()
    {
        //deletion stuff
        pA = NULL;
    }
}

Несмотря на то, что это многословно, я думаю, что это должно безопасно фальсифицировать наследование и позволить вам подождать, чтобы построить A, пока B не выполнит свою задачу. Он также инкапсулирован, поэтому, когда вы можете тянуть A, вам не нужно менять ничего, кроме A и B.

Кроме того, вы также можете сделать несколько шагов назад и спросить себя; Какую функциональность дает мне наследование, которое я пытаюсь использовать, и как я могу достичь этого другими способами? Например, CRTP может использоваться как альтернатива virtual, а политика - как альтернатива наследованию функций. (Я думаю, что это правильное выражение). Я использую эти идеи выше, просто отбрасываю шаблоны b / c. Я ожидаю, что A будет только шаблоном на B и наоборот.

...