避免在构造函数中使用虚方法

时间:2011-07-05 11:55:44

标签: c++

假设我有以下类层次结构:

class Base
{
   virtual int GetClassID(){ return 0;};
public:
   Base() { SomeSingleton.RegisterThisObject(this->GetClassID());
}

class Derived
{
   virtual int GetClassID(){ return 1;};
public:
   Derived():Base(){};
}

嗯,这完全是从我的实际案例中简化出来的,但这是它的一般要点。

我想避免在每个派生类的构造函数中调用RegisterThisObject,所以我试图将调用移动到基类的构造函数。

在构造函数中不使用虚方法的情况下,是否可以使用任何模式来完成此操作?

4 个答案:

答案 0 :(得分:8)

您可以使用奇怪的重复模板模式

template <class T>
class Base
{
protected:  // note change
   Base() { SomeSingleton.RegisterThisObject(T::GetClassID());
}

class Derived : Base<Derived>
{
   static int GetClassID(){ return 1;};
public:
   Derived(): Base<Derived>(){};
}

此外,如果您有多个派生类(例如DerivedDerived : Derived),则需要额外的工作。我建议您简单地避免,但在其他情况下,您可能希望将注册转移到策略类中(使行为可聚合而不是类标识的一部分)

性状

扩展我的提示(使行为可聚合),你会看到类似这样的事情:

namespace detail
{
    template <class T> struct registerable_traits { };         
    template<> struct registerable_traits<Derived>
    { 
        enum _id { type_id = 1 };
    };
}

template <class T>
class Base
{
protected:  // note change
   Base() { SomeSingleton::RegisterThisObject(detail::registerable_traits<T>::type_id); }
};

class Derived : Base<Derived>
{
public:
   Derived(): Base<Derived>(){};
};

请参阅Codepad.org

答案 1 :(得分:7)

virtual方法的问题在于它不起作用,因为在执行基础构造函数对象时,对象的类型是基础,而不是派生类型。

如果GetClassID是静态成员函数,您可以更改设计,以便将标识符作为参数传递给基类型:

struct Base {
   Base( int id ) {
      register_object( id, this );
   }
};
struct Derived {
   static int getId() { return 5; }
   Derived() : Base( getId() ) {}
};

答案 2 :(得分:3)

这个确切案例的最简单的解决方案就是将id作为一个传递 Base的参数,并用它来完成。只要它只是一个 数据问题,不需要虚函数。更复杂 例如,您可以将指针传递给struct,甚至是地址 (静态成员或免费)功能。

在更复杂的情况下,策略模式可能适用:实际 自定义被委托给单独的层次结构,并派生 类构造函数传递指向派生委托的指针。如果 委托没有状态,它可以是某个地方的静态实例。

最后,如果所有其他方法都失败了,您可以使用伪参数(如果派生的话 没有参数)或包装参数。这需要一些合作 来自派生类,并不适合临时工,但我已经 成功使用了一次或两次。基本上,你定义的东西 像:

class Base
{
public:
    class DeferredInit
    {
        friend class Base;
        mutable Base* myOwner;
    public:
        DeferredInit() : myOwner( NULL ) {}
        ~DeferredInit()
        {
            if ( myOwner != NULL ) {
                myOwner->postCtor();
            }
        }
    };
    Base( DeferredInit const& initializer )
    {
        initializer.myOwner = this;
    }
};

派生类类似于:

class Derived : public Base
{
public:
    Derived( Base::DeferredInit const& fromAbove = Base::DeferredInit() )
        : Base( fromAbove )
    {
    }
};

有一次我使用了这个,所有的课程都接受了std::string 作为输入,所以我安排DeferredInit隐式转换 包装参数的std::stringchar const*。再次, 客户端代码可以写:

Derived d( "some string" );
在完整表达式结束时调用了

postCtor。 (那是 为什么这样的事情:

Derived( "abc" ).doSomething();

不行。您必须声明一个实例以确保postCtor是 在以任何其他方式使用对象之前调用。没有临时工!)

但我只考虑这个解决方案作为最后的手段。它介绍 额外的复杂性,并增加了对临时的限制。

答案 3 :(得分:1)

我会将寄存器放入成员函数,并要求人们明确地调用它:

  class Base {
    void reg() { SomeSingleton.RegisterThisObject(GetClassID()); }
  };

构造函数不是这种东西的正确位置:

int main() {
  Derived d;
  d.reg();
}