Безопасный способ инициализации производного класса - PullRequest
4 голосов
/ 15 сентября 2009

У меня есть базовый класс:

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() }
};

Это не совсем безопасно, потому что я хочу, чтобы конструктор с параметром false был защищен, поэтому вызывать его могут только производные классы. Но тогда мне придется создать второй конструктор (который защищен) и заставить его принимать другие параметры (возможно, неиспользуемые, потому что конструктор вызывается, когда не нужно вызывать 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(); }
};

Мне кажется, невозможно, чтобы два конструктора с одинаковыми аргументами были защищены и общедоступны?

Ответы [ 6 ]

3 голосов
/ 15 сентября 2009

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

То, что вы ищете, это шаблон проектирования 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;
}
3 голосов
/ 15 сентября 2009

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

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

2 голосов
/ 15 сентября 2009

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

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

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 в производных классах, тогда как эти проверки все еще должны выполняться в конструкторе. Обычно это признак сломанной архитектуры; на самом деле вы пытаетесь поместить некоторые сведения о производном классе в родительский объект, что, как правило, неверно.

1 голос
/ 15 сентября 2009

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

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

protected: CBase(boost::function<void(*)(CBase*)> SomeChecks) {
    // Base checks
    SomeChecks(this); // Checks provieded by derived ctor but running on CBase member.
}
0 голосов
/ 15 сентября 2009

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

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 голосов
/ 15 сентября 2009

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

Другая проблема, вызывающая другой конструктор с такой же сигнатурой аргумента, может быть решена с помощью трюка «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) {}
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...