2009-09-15 8 views
4

У меня есть базовый класс:Безопасный способ для инициализации производного класса

class CBase { 
    public: 
     virtual void SomeChecks() {} 
     CBase() { 
     /* Do some checks */ 
     SomeChecks(); 
     /* Do some more checks */ 
     } 
}; 

и производный класс:

class CDerived : public CBase { 
    public: 
     virtual void SomeChecks() { /* Do some other checks */ } 
     CDerived() : CBase() {} 
}; 

Эта конструкция, кажется, немного странно, но в моем случае это требуется , потому что CBase делает некоторые проверки и CDerived может смешать некоторые проверки между ними. Вы можете видеть это как способ «зацепить» функции в конструкторе. Проблема с этой конструкцией заключается в том, что при построении CDerived сначала создается CBase, и нет осознания CDerived (поэтому перегруженная функция SomeChecks() не называется).

я мог бы сделать что-то вроде этого:

class CBase { 
    public: 
     void Init() { 
     /* Do some checks */ 
     SomeChecks(); 
     /* Do some more checks */ 
     } 
     virtual void SomeChecks() {} 
     CBase(bool bDoInit=true) { 
     if (bDoInit) { Init(); } 
     } 
}; 
class CDerived : public CBase { 
    public: 
     virtual void SomeChecks() { /* Do some other checks */ } 
     CDerived() : CBase(false) { Init() } 
}; 

Это не очень безопасно, потому что я хочу, конструктор с параметром ложному быть защищены, так что только производные классы могут назвать. Но тогда мне придется создать второй конструктор (который защищен) и заставить его принимать другие параметры (возможно, неиспользуемые, потому что вызывается конструктор, когда Init() не нужно вызывать).

Так что я совершенно застрял здесь.

EDIT На самом деле я хочу что-то вроде этого:

class CBase { 
    protected: 
     void Init() { /* Implementation of Init ... */ } 
     CBase() { /* Don't do the Init(), it is called by derived class */ } 
    public: 
     CBase() { Init(); }  // Called when an object of CBase is created 
}; 
class CDerived : public CBase { 
    public: 
     CDerived() : CBase() { Init(); } 
}; 

Мне кажется, что невозможно иметь 2 конструктора с теми же аргументами, защищаемого и общественности?

ответ

3

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

Что вы ищете является Pimpl дизайн шаблона:

class CBase { ... }; 
class CDerived: public CBase { ... } 

template<typename T> 
class PIMPL 
{ 
    public: 
     PIMPL() 
      :m_data(new T) 
     { 
      // Constructor finished now do checks. 
      m_data->SomeChecks(); 
     } 
     // Add appropriate copy/assignment/delete as required. 
    private: 
     // Use appropriate smart pointer. 
     std::auto_ptr<T> m_data; 
}; 
int main() 
{ 
    PIMPL<CDerived> data; 
} 
+0

Как указано, вызов виртуальных методов в конструкторах не приводит к желаемому поведению. Я думаю, что решение pimpl является наиболее разумным. Тема хорошо освещена в книге Скотта Майера «Эффективная книга на Си ++». – count0

+0

Собственно, это не прыщик. По крайней мере, так называется так. См. Здесь: http://en.wikipedia.org/wiki/Opaque_pointer#C.2B.2B – sbi

+0

На самом деле это так. :-) Прочитайте свою страницу вики. Но хорошая книга по шаблонам проектирования, например, GOF, вероятно, будет лучше. –

2

Вы делаете что-то довольно странное здесь.

Следующая будет работать и полностью безопасен:

class CBase { 
     void SomeChecks() {}; 
    public: 
     CBase() { 
     /* Do some checks */ 
     SomeChecks(); 
     } 
}; 

class CDerived: public CBase{ 
     void SomeOtherChecks() {}; 
    public: 
     CDerived() { 
     /* Do some other checks */ 
     SomeOtherChecks(); 
     } 
}; 

В этой иерархии, когда строится CDerived, первый CBase выполняет свои SomeChecks(), а затем CDerived делает свои собственные OtherChecks(). Вот как это должно быть.

Тот факт, что вы сделали SomeChecks() виртуальным, показывает намерение разрешить SomeChecks полностью переопределяться в производных классах, в то время как эти проверки все равно должны выполняться в конструкторе. Это обычно указывает на разрушенную архитектуру; на самом деле, вы пытаетесь поместить некоторые знания производного класса в родителя, что, как правило, неверно.

+0

Мой вопрос может быть не полным. В моем случае конструктор читает XML-файл и проверяет элементы. Производный класс может добавлять элементы в XML, где база ничего не знает. Поэтому, когда CBase() проходит через элементы, он пересылает элементы в CDerived :: SomeOtherChecks() для обработки этих элементов. Я согласен с «сломанной архитектурой», такой вещи следует избегать. Возможно, я перехожу к более сложной проблеме, чем требуется ... :-) – To1ne

+2

Почему бы не повернуть это. Попросите производный конструктор проверить элементы и переслать все, что он не знает о базовом классе. – KeithB

1

Для этого нет чистого решения. Вы не можете безопасно выполнять функции CDerived до тех пор, пока не будет введен CDerived ctor body. В этот момент CBase ctor, должно быть, вернулся.

Один из способов может быть следующее:

protected: CBase(boost::function<void(*)(CBase*)> SomeChecks) { 
    // Base checks 
    SomeChecks(this); // Checks provieded by derived ctor but running on CBase member. 
} 
3

То, что вы хотите, называется две фазы строительства. C++ не предлагает это как синтаксическую конструкцию, поэтому вы должны делать это самостоятельно.

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

0

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

Другая проблема, вызывающая другой конструктор с той же сигнатурой аргумента, может быть решена с помощью трюка «tagged constructor»: создать конструктор шаблона.

struct C { 
    enum eConstructor { cCheck, cNoCheck }; 

    template< eConstructor constr = cCheck > C(int i); 

    int positive_value; 
}; 

template<> C::C<C:: cCheck >(int i) : positive_value(std::max(0, i)) { } 
template<> C::C<C::cNoCheck>(int i) : positive_value(i) { } 


struct CFive : public C { 
    CFive(): C<C::cNoCheck>(5) {} 
}; 
-1

Может быть, это работает для вас:

class CBase { 
    public: 
     CBase() { 
     /* Do some checks */ 
     SomeChecks(); 
     /* Do some more checks */ 
     } 
     virtual ~CBase(){} /*don't forget about this*/ 
     virtual void SomeChecks() {} 
}; 


class CDerived : public CBase { 
    public: 
     void SomeChecks() { //without virtual 
      /* Do some other checks */ 
      CBase::SomeChecks(); //if you want checks from CBase 
     } 
     CDerived() : CBase() {} 
}; 

CBase* fromBase = new CBase(); //checking with CBase::SomeChecks() 
CBase* fromDerived = new CDerived(); //checking with CDerived::SomeChecks 
CDerived* alsoDerived = new CDerived(); //checking with CDerived::SomeChecks 
+0

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

+1

Тот факт, что я узнаю только по моим ошибкам, очевиден, потому что я не столкнулся с этой ловушкой C++. Я помню об этом в книге, но никогда не удосужился ее запомнить. Хорошо знать. – grayasm

+0

@vmihai: Вы посмотрели на Скотта Мейерса «Эффективный C++»? Хороший набор правил (IIRC, это 53 в последнем выпуске), хорошо представленный, тщательно объясненный, легко запоминающийся. Бьюсь об заклад, один из них не вызывает виртуальные функции от конструкторов, а деструкторы - один из них. – sbi