初始化派生类的安全方法

时间:2009-09-15 07:35:54

标签: c++

我有一个基类:

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()时调用构造函数。)

所以我完全被困在这里。

修改 其实我想要这样的东西:

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个构造函数是不可能被保护和公开的?

6 个答案:

答案 0 :(得分:3)

你想要的是两相结构。 C ++不提供这个作为语法结构,所以你必须自己做。

执行此操作的常用方法是使用Programmer的All-Purpose Ointment(添加另一层间接):将类包装在其他类中。该类的构造函数首先调用类的构造函数,然后调用其他初始化函数。当然,这会弄乱你的设计。

答案 1 :(得分: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;
}

答案 2 :(得分: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,而这些检查仍然必须在构造函数中执行。这通常表明架构破碎;实际上,您正试图将一些派生类的知识放在父级中,这通常是错误的。

答案 3 :(得分:1)

对此没有干净的解决方案。在输入CDerived ctor正文之前,您无法安全地调用CDerived函数。此时,CBase ctor必须已经返回。

一种解决方法可能如下:

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

答案 4 :(得分: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) {}
};

答案 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