如何处理构造函数内部的`init()`成员函数和继承问题

时间:2018-11-29 12:20:14

标签: c++ inheritance constructor

想象一下这些课程:

class Base {
public:
    Base() : Base(false)
    { }
    virtual ~Base() = default;

    void init()
    {
        cout << "Base::init" << endl;
        check();
        // ...
    }

    virtual void check()
    {
        cout << "Base::check" << endl;
        // ...
    }
protected:
    Base(bool skip_init)
    {
        cout << "Base::Base" << endl;
        if (!skip_init) init();
    }
};

class Derived : public Base {
public:
    Derived() : Base(true)
    {
        cout << "Derived::Derived" << endl;
        init();
    }
    virtual ~Derived() = default;

    void init()
    {
        cout << "Derived::init" << endl;
        Base::init();
        // ...
    }

    virtual void check() override
    {
        cout << "Derived::check" << endl;
        Base::check();
        // ...
    }
};

然后构造一个Derived实例将导致

Base::Base
Derived::Derived
Derived::init
Base::init
Derived::check
Base::check

这正是我想要实现的目标。 满足以下要求:

  • Base用所有子类通用的操作定义init(),应在构造 whole 对象(并且在那里仅 )后立即使用< / li>
  • init()可以在内部包含virtual个函数,但是由于仅应在最终构造函数中调用它,因此不会造成任何危害
  • check()可以随时调用,不仅可以从init()中调用(它应该独立于此),而且应该始终执行所有检查,不仅是与子类相关的检查

到目前为止,到目前为止,我最好的方法是将受保护的构造函数与标志一起使用,以避免由于虚函数在超类构造函数中不起作用而调用“不完整” Base::init()。 (没有该标志,Base::check()将被调用两次。)

我的问题是:在初始化整个对象之后,是否存在一种更好的,最好是某种程度上适合标准技术的方法来处理虚拟例程(请用模糊的术语表示宽恕)?当然,无需用户显式调用init()(它应该受到保护)。

一个可能的用例(我的):Base代表例如一组必须满足多个约束的通用数学公式。 Derived(即是)限制了这些约束,添加了一些约束,可以覆盖某些特定的检查,但是大多数情况下仍使用Base中的检查。例如。 Base::check_formulas()check_formula(f)应用于每个f,并且Derived仅需要覆盖check_formula函数。

编辑

由于最好完全避免在构造函数内部使用虚函数,因此似乎不可能从对象本身内部实现虚函数调用,因此在调用这些函数之前必须“外部”构造对象。

@StoryTeller和@Caleth都建议通过动态分配和指针,或通过具有堆栈分配的函数(对于移动语义来说也可以)来解决此问题。

他们两个都启发了我这个解决方案,它类似于@Caleth的解决方案,因为我发现它更简单明了:

template <typename T, typename... Args>
T create(Args&&... args)
{
    T t(forward<Args>(args)...);
    t.init();
    return t;
}

class Base {
public:
    virtual ~Base() = default;
    Base(const Base& rhs) = default;
    Base(Base&& rhs) = default;
    Base& operator=(const Base& rhs) = default;
    Base& operator=(Base&& rhs) = default;

    template <typename T, typename... Args>
    friend T create(Args&&... args);
protected:
    Base() : _arg(0)
    {
        cout << "Base::Base()" << endl;
    }

    Base(int arg) : _arg(arg)
    {
        cout << "Base::Base(int)" << endl;
    }

    virtual void init()
    {
        cout << "Base::init" << endl;
        check();
        // ...
    }

    virtual void check()
    {
        cout << "Base::check" << endl;
        // ...
    }
private:
    int _arg;
};

class Derived : public Base {
public:
    virtual ~Derived() = default;

    template <typename T, typename... Args>
    friend T create(Args&&... args);
protected:
    Derived() : Base()
    {
        cout << "Derived::Derived()" << endl;
    }

    Derived(int arg) : Base(arg)
    {
        cout << "Derived::Derived(int)" << endl;
    }

    void init() override
    {
        cout << "Derived::init" << endl;
        Base::init();
        // ...
    }

    void check() override
    {
        cout << "Derived::check" << endl;
        Base::check();
        // ...
    }
};

用法:

cout << endl << "Base() ..." << endl;
Base b1 = create<Base>();
cout << endl << "Base(int) ..." << endl;
Base b2 = create<Base>(5);
cout << endl << "Derived() ..." << endl;
Derived d1 = create<Derived>();
cout << endl << "Derived(int) ..." << endl;
Derived d2 = create<Derived>(10);

输出:

Base() ...
Base::Base()
Base::init
Base::check

Base(int) ...
Base::Base(int)
Base::init
Base::check

Derived() ...
Base::Base()
Derived::Derived()
Derived::init
Base::init
Derived::check
Base::check

Derived(int) ...
Base::Base(int)
Derived::Derived(int)
Derived::init
Base::init
Derived::check
Base::check

还有其他建议吗?

3 个答案:

答案 0 :(得分:4)

就个人而言,我只是不允许任何人直接构造那些对象。如果它们的初始化很脆弱,则应该有另一个对象来保存它们,并在自己的构造函数中对其进行初始化。我会通过密钥传递习惯来做到这一点。

class BaseHolder;
class Base {
private:
    void init() {
      // do more things
    }
    friend class BaseHolder;
protected:
    class BuildToken {
        explicit BuildToken() {}
        friend class BaseHolder;
    };
    Base(BuildToken) {
      // do your thing.
    }
};

template<typename>
struct MakeType{};

template<typename T>
inline constexpr MakeType<T> make{};

class BaseHolder {
    std::unique_ptr<Base> managed;
public:
    template<typename T>
    BaseHolder(MakeType<T>) : managed(new T(Base::BuildToken())) {
      managed->init();
    }
};

现在,没有派生类可以自己调用init,也不能被BaseBaseHolder调用。派生类所要做的就是定义一个接受BuildToken的c'tor并将其转发给基类。但是,派生类无法默认初始化BuildToken对象本身,它们只能将其复制以转发到其基类上。可以创建令牌的唯一位置是BaseHolder。这也是唯一在适当时间调用init的地方。

使用实用程序MakeType可以使BaseHolder声明看起来更容易:

BaseHolder item(make<Derived>);

答案 1 :(得分:2)

没有任何公共构造函数,而是具有(朋友)make_basemake_derived工厂函数,它们在完全构造的对象上调用init

class Base {
public:
    virtual ~Base() = default;
    Base(const Base &) = default;
    Base(Base &&) = default;
    Base& operator=(const Base &) = default;
    Base& operator=(Base &&) = default;

    friend Base make_base() { Base b; b.init(); return b; } 

protected:
    virtual void init()
    {
        cout << "Base::init" << endl;
        check();
        // ...
    }

    virtual void check()
    {
        cout << "Base::check" << endl;
        // ...
    }

    Base()
    {
        cout << "Base::Base" << endl;
    }
};


class Derived : public Base {
    friend Derived make_derived() { Derived b; b.init(); return b; }
protected:
    Derived() : Base()
    {
        cout << "Derived::Derived" << endl;
    }

    void init() override
    {
        Base::init();
        cout << "Derived::init" << endl;
        // ...
    }

    void check() override
    {
        Base::check();
        cout << "Derived::check" << endl;
        // ...
    }
};

答案 2 :(得分:1)

  
      
  • init()可以在内部包含虚函数,但是由于仅应在最终构造函数中调用它,因此不应导致   任何伤害。
  •   

即使是通过 final构造函数调用的,任何用于调用这些虚拟函数的vtable都不会在此时初始化。

所以您不能保证正确的预期行为。

请在此处查看有关该问题的更多详细信息: