施工期间的虚拟功能

时间:2014-04-25 20:59:59

标签: c++ constructor virtual

我有一个具有虚函数的基类。我想在构造期间调用该类,因为我想要为每个派生类调用该函数。我知道在构造过程中我不能调用虚函数,但我想不出优雅(即避免重复代码)的解决方案。

在施工期间调用虚拟功能有哪些方法?

我想避免这种情况的原因是因为我不想创建只调用基类的构造函数。

class A {
    public:
        A() {
            read();
        }

        // This never needs to be called
        virtual void read() = 0;
}

class B:A {
    public:
        B():A() {   };
        read() { /*Do something special for B here.*/ }

}

class C:A {
    public:
        C():A() {   };
        read() { /*Do something special for C here.*/ }

}

PS:Python的做法只是raise NotImplementedError中的A::read()。我回到了C ++,我比我想象的更生疏。

5 个答案:

答案 0 :(得分:1)

实现此目的的一种方法,只是将其委托给另一个类(可能是朋友),并且可以确保在完全构造时调用它。

class A
{
friend class C;
private:
    C& _c; // this is the actual class!    
public:
    A(C& c) : _c(c) { };
    virtual ~A() { };
    virtual void read() = 0;
};


class B : public A
{
public:
    B(C& c) : A(c) { };
    virtual ~B() { };

    virtual void read() { 
       // actual implementation
    };
};


class C
{
private:
    std::unique_ptr<A> _a;

public:
    C() : _a(new B(*this)) { // looks dangerous?  not at this point...
        _a->read(); // safe now
    };
};

在这个例子中,我只是创建一个B,但是你如何做到这一点取决于你想要实现的目标,并在必要时在C上使用模板,例如:

template<typename VIRTUAL>
class C 
{
private:
   using Ptr = std::unique_ptr<VIRTUAL>;

   Ptr _ptr;
public:
   C() : _ptr(new VIRTUAL(*this)) {
       _ptr->read();
   };
}; // eo class C

答案 1 :(得分:1)

这是工厂方法方法,将工厂放入基类:

class A {
public:
    virtual void read() = 0;
    template<class X> static X* create() {X* r = new X;X->read();return X;}
    virtual A* clone() const = 0;
};

class B : public A {
    B():A() {   };
    friend class A;
public:
    void read() { /*Do something special for B here.*/ }
    B* clone() const {return new B(*this);}
};

class C : public A {
    C():A() {   };
    friend class A;
public:
    void read() { /*Do something special for C here.*/ }
    C* clone() const {return new C(*this);}
};

添加了clone - 方法,其中包含协变返回类型作为奖励。

使用CRTP

class A {
public:
    // This never needs to be called
    virtual void read() = 0;
    virtual A* clone() const = 0;
};
template<class D, class B> struct CRTP : B {
    D* clone() {return new D(*this);}
    static D* create() {return new D();}
};

class B : public CRTP<B, A> {
    B() {   };
public:
    void read() { /*Do something special for B here.*/ }
};

class C : public CRTP<C, A> {
    C() {   };
public:
    void read() { /*Do something special for C here.*/ }
};

答案 2 :(得分:1)

常见问题解答视角。

这是一个常见问题。

请参阅标题为“Okay, but is there a way to simulate that behavior as if dynamic binding worked on the this object within my base class's constructor?”的C ++ FAQ项目。

在询问之前检查常见问题解答(通常是谷歌搜索或altavista')通常是一个好主意。

<小时/>

问题为“派生类特定基础初始化”。

要清楚,虽然上面的字面问题是

  

“在施工过程中调用虚函数有哪些工作原理?”

很明显,这意味着

“如何设计基类B,以便每个派生类可以指定B构建期间发生的部分内容?”

一个主要的例子是C风格的GUI功能由C ++类包装。然后,通用Widget构造函数可能需要实例化API级别的小部件,该小部件取决于最派生的类,应该是按钮小部件或列表框小部件或其他。因此,派生程度最高的类必须以某种方式影响Widget构造函数中的内容。

换句话说,我们正在讨论派生类特定基础构造

Marshall Cline称之为“构造期间的动态绑定”,并且它在C ++中存在问题,因为在C ++中,类T构造和销毁期间对象的动态类型是T。这有助于类型安全,因为在初始化该子对象或其初始化已开始之前,不会在派生类子对象上调用虚拟成员函数。但是主要成本是DBDI(显然)不能以简单和安全的方式完成。

<小时/>

可以执行派生类特定的init。

在问题中,派生类特定操作被称为read。在这里我称之为derived_action。调用derived_action的位置有三种主要可能性:

  • 由实例化代码调用,称为两阶段构造 这基本上意味着有一个非常不可用的未完全初始化的对象,一个僵尸对象。但是,随着C ++ 11移动语义变得越来越普遍和被接受(无论如何,它可以通过使用工厂在某种程度上得到缓解)。一个主要问题是,在构建的第二阶段,由于构造期间的动态类型更改,不存在针对未初始化子对象的虚拟调用的普通C ++保护。

  • Derived构造函数调用。
    例如,derived_action可以作为Base构造函数的参数表达式调用。一种并非完全不常见的技术是使用类模板来生成大多数派生类,例如,提供derived_action

  • 的来电
  • Base构造函数调用。
    这意味着derived_action的知识必须动态或静态地传递给构造函数。一种不错的方法是使用默认的构造函数参数。这导致了并行类层次结构的概念,即派生类操作的层次结构。

此列表是为了提高复杂程度和类型安全性,并且据我所知,反映了各种技术的历史用途。

E.g。在微软的MFC和Borland的ObjectWindows GUI 1990年初,图书馆的两阶段建设很常见,截至2014年,这种设计现在被视为非常不合理。

答案 3 :(得分:0)

解决方法是在构建之后调用虚拟函数。然后,您可以在工厂功能中将两个操作(构造+虚拟调用)耦合在一起。这是基本的想法:

class FactoryA
{
public:
    A *MakeA() const
    {
        A *ptr = CreateA();
        ptr->read();
        return ptr;
    }

    virtual ~FactoryA() {}

private:
    virtual A *CreateA() const = 0;
};

class FactoryB : public FactoryA
{
private:
    virtual A *CreateA() const { return new B; }
};

// client code:

void f(FactoryA &factory)
{
    A *ptr = factory.MakeA();
}

答案 4 :(得分:-1)

如Benjamin Bannier所述,您可以使用CRTP(定义实际read()函数的模板。)该方法的一个问题是模板必须始终以内联方式编写。这有时会成为问题,特别是如果你要编写非常大的函数。

另一种方法是将函数指针传递给构造函数。虽然在某种程度上,它类似于在构造函数中调用函数,但它会强制您传递指针(尽管在C ++中,您总是可以传递nullptr。)

class A
{
public:
    A(func_t f)
    {
        // if(!f) throw ...;
        (*f)();
    }
};

class B : A
{
public:
    B() : A(read) {}

    void read() { ... }
};

显然,你在read()函数及其调用的任何函数中都有“无法调用其他虚函数”的问题。另外,B的可变成员尚未初始化。在这种情况下,这可能是一个非常严重的问题......

因此,以这种方式编写更安全:

   B() : A()
   {
       read();
   }

但是,在这种情况下,这可能是你使用init()函数的时候。 init()函数可以在A()中实现(如果你可以访问它:即在派生时使用public A)并且该函数可以按预期调用所有虚函数:

class A
{
public:
    void init()
    {
        read();
    }
};

class B : public A
{
public:
    ...
};

我知道很多人都说init()函数是邪恶的,因为创建B对象的人现在需要知道调用它...但是你没有其他的东西可以做。话虽这么说,你可以有一种工厂形式,工厂可以根据需要进行init()调用。

class B : public A
{
public:
    static B *create() { B *b(new B); b->init(); return b; }

private:
    B() { ... } // prevent creation without calling create()
};