如何避免两步初始化

时间:2017-01-07 02:45:17

标签: c++ inheritance

我希望在我的代码中遵循RAII(资源获取是初始化)习惯用法,但我也在做模板方法模式,我正在开发我的类的通用版本并使用它们为某些事情构建一个通用的代码库。有时我需要强制执行一个初始化序列,我需要在构造函数中调用专用对象的虚函数,但这在C ++中是不可能的。我能想到的唯一解决方案是在创建对象之后通过调用init函数进行两步初始化,但这会打破RAII习惯用法。这有什么解决方案吗?

#include <memory>

class A
{
public:
    A()
    {
        // I want to call B's foo() here
    }
    virtual void foo() = 0;
};

class B : public A
{
public:
    B() {}
    virtual void foo() {}
};

int main()
{
    std::unique_ptr<A> a(static_cast<A*>(new B));

    // Use b polymorphically from here...
}

编辑:似乎我的例子太简单了,这是另一个可能更好地说明我尝试做的事情:

#include <memory>
#include <iostream>

class A
{
public:
    A()
    {
        foo();
    }
    void init()
    {
        // bar() must be executed every time A is created but the derived classes will decide how to implement it
        bar();
    };
    void foo() { std::cout << "Part 1 of algo\n";  };
    virtual void bar() = 0;
};

class B : public A
{
public:
    B() {}
    virtual void bar() { std::cout << "Part 2 of algo\n"; }
};

int main()
{
    std::unique_ptr<A> a(static_cast<A*>(new B));
    a->init(); // I don't want to have to call this every time I instanciate it
}

2 个答案:

答案 0 :(得分:1)

请勿使用虚拟分派。

A(std::function<void(A*)> foo)
{
    foo(this);
}

A(std::function<void()> foo)
{
    foo();
}

现在A的子类型将foo的正文传递给A ctor。当然,foo最好不要依赖有效B甚至A

您可以将构造限制为私有/受保护的路径,并在其最终的伪构造函数中需要分阶段初始化。也许有一个CRTP助手,后代应该用它来实现定相(DRY)。

但是这个要求是一个红旗,你的类型可能充满了意大利面条逻辑。有时需要,但通常可以避免。

另一种完全不同的方法是抛弃特定的C ++内置OO系统,如果它不符合您的需求。有无数种方法来实施OO;假设内置的C ++方式最适合您的问题是锤钉解决方案(你有一把锤子,所以这个问题就像钉子一样)。

例如,如果您将类型规范化并将继承作为实现细节放置,或者编写自己的vtable,那么问题就会出现问题&#34;两阶段的init可以消失(因为这些在另一层抽象中包装init)。

学习如何惯用OO&#34;工作&#34;在Smalltalk,Perl,C,Python,C#/ COM / .net,MFC,Objective-C,Haskell,Javascript和Lua(作为我头脑中的一组例子)应该各自为您提供一种不同的方法a&#34;手册&#34; OO解决方案(有一些重叠)。

答案 1 :(得分:0)

这是我的解决方案,使用静态工厂函数创建B(并且可能是通过使用模板的任何类型的派生类),在构造之后调用onNew()。创建B的唯一方法是调用创建函数,该函数实例化B并返回一个共享指针,这个共享指针有一个自定义删除器,它调用&#34; destroy&#34;功能自动。这允许完成构造后和预破坏工作(也可以针对其他类型的指针进行修改)。

对于那些仍然困惑的人,为什么我这样做,这是因为:

  • 我可以通过调用onNew()和onDelete()中的函数来强制执行A​​中的初始化和销毁​​序列(如foo()和bar())

  • 必须按照我想要的顺序调用这些函数,但它们必须由派生类定义

  • 这为派生类的创建者留下了很小的错误空间,因为它们的实现非常严格

  • 在使用类时也没有留下任何错误的余地,因为用户不必手动调用onNew()和onDelete(),它们都是自动完成的。

    < / LI>
  • 我可以向上转换为A并以多态方式使用它,并且更有信心它处于有效状态且已正确初始化,在创建对象后无需进一步检查。

  • 在onNew()中,我还可以从foo()返回一个值,并将其设置为成员变量,并在其他函数中对其进行操作。在这些函数中,我不需要检查那些成员变量是否已正确设置,因为它在构造后强制执行。

-

#include <type_traits>
#include <memory>
#include <iostream>

using namespace std;

class A 
{
protected:
    A() {}
    virtual ~A() {}

    virtual void onNew() 
    {
        foo();
    }

    virtual void onDelete() 
    {
        bar();
    }

    void destroy() 
    {
        onDelete();
        delete this;
    }

public:
    template <class T> static shared_ptr<T> create() 
    {
        static_assert(std::is_base_of<A, T>::value, "T must be a descendant of A");
        shared_ptr<T> t(new T(), [](T *p) { p->destroy(); });
        t->onNew();
        return t;
    }

    virtual void foo() = 0;
    virtual void bar() = 0;
};

class B : public A 
{
    friend A;

protected:
    B() {}
    virtual ~B() {}

public:
    virtual void foo() override 
    {
        cout << "B foo";
    }

    virtual void bar() override
    {
        cout << "B bar";
    }
};

int main() 
{
    shared_ptr<A> a = static_cast<shared_ptr<A>>(A::create<B>());
}