强制子类实现并调用(一次)方法

Force sub-classes to implement and call (once) a method

本文关键字:一次 方法 调用 子类 实现      更新时间:2023-10-16

我正在寻找一种方法来创建必须由每个子类实现的方法。我还希望子类在构造上调用此方法。

在类构造之后应该无法再次调用此方法。

#include <iostream>
class Base {
public:
    Base() {init();}
private:
    virtual void init() = 0;
};
class DerivedA : public Base {
public:
    DerivedA() {}
private:
    virtual void init() { std::cout << "Hello, I am A.";}
};
class DerivedB : public Base{
public:
    DerivedB() {}
private:
    virtual void init() {std::cout << "Hello, I am B.";}
};
int main(){
    DerivedA a;
    DerivedB b;
    return 0;
}

这是一个示例,但它无效,因为它在构造函数中调用纯虚拟方法。当然,我可以在每个子类构造函数中添加init(),但是在新子类上可能会忘记它。

这样做的C++方法是什么?

C++方法是这样做。初始化函数不好。只需使用构造函数。

AFAIK,在构造函数中调用虚函数是非常危险的。下面是一个简单的示例。我稍微修改了您的代码,以使该方法也Base类中实现init

#include <iostream>
#include <exception>
class Base {
protected:
    Base() {init() ; }
    virtual void init() {
        std::cout << "Init base" << std::endl;
    }
public:
    void callinit() {
        init();
    }
};
class DerivedA : public Base {
public:
    DerivedA() {}
protected:
    virtual void init() { std::cout << "Hello, I am A."<< std::endl;}
};
class DerivedB : public Base{
public:
    DerivedB() {}
protected:
    virtual void init() {std::cout << "Hello, I am B."<< std::endl;}
};
int main(){
    DerivedA a;
    DerivedB b;
    a.callinit();
    b.callinit();
    return 0;
}

输出为:

Init base
Init base
Hello, I am A.
Hello, I am B.

我们可以得出什么结论:

  • 一旦对象被构造,一切都很好,当我们调用init方法时,我们通常会从派生类中获得正确的实现
  • 但在构造函数中,顺序是:

    • 调用构造Base函数
    • 从 Base 对象调用 init 方法(因为派生对象尚未构造)
    • 调用构造DerivedX

    因此,该方法始终是来自Base的方法,这绝对不是您所期望的。

正如另一张海报所说,您可能应该远离这种情况,但最简单的示例是在 Base 上创建一个名为 Init() 的公共非虚拟接口方法,该方法必须在对象构造后调用。 该方法可以在派生类上调用纯虚拟"DoInit"方法,并使用内部标志跟踪是否已调用该方法。

我不建议这样做,但它会起作用。

class Base
{
public:
    void Init()
    {
        if(!initialized)
        {
            DoInit();
            initialized = true;
        }
    }
protected:
    virtual void DoInit() = 0; // derived classes need to implement this
private:
    bool initialized {false};
};

我遇到了类似的问题,找不到简单的解决方案。我必须在单独的类中进行初始化。此类的对象可以传递给基/派生构造函数,或者此类可以是模板参数。

class Initializer {
    . . .
}
class Base {
public:
    Base(Initializer* initializer) {
        // Get members from initializer
    }
}

或:

template<Initializer TInitializer>
class Base<TInitializer> {
public:
    Base() {
        TInitializer initializer;
        // Get members from initializer
    }
}

抱歉,我没有写太长C++,所以我可以防止一些语法错误。

C++11 的call_once可以让你大部分时间,但它有成本。

  1. 该类将不可移动或复制。
  2. 您必须在每个需要初始化的函数中添加一行额外的行。

它不会阻止多次调用该方法,但这很容易添加。

#include <iostream>
#include <mutex>
struct Base {
    Base() {
        std::cout << "Base ctor" << std::endl;
    }
    void sampleFunction1() {
        // this line must be at the start of every function that needs the initialization
        std::call_once(initedFlag, &Base::v_init, this);
        std::cout << "Base::sampleFunction1" << std::endl;
    }
    void sampleFunction2() {
        // this line must be at the start of every function that needs the initialization
        std::call_once(initedFlag, &Base::v_init, this);
        std::cout << "Base::sampleFunction2" << std::endl;
    }
private:
    virtual void v_init() = 0;
    std::once_flag initedFlag;
};

请注意,派生类除了提供v_init之外,没有任何特殊之处。

struct Derived : Base {
    Derived() {
        std::cout << "Derived ctor" << std::endl;
    }
private:
    void v_init() override {
        std::cout << "Derived::v_init" << std::endl;
    }
};

演示代码

int main(int argc, const char * argv[]) {
    Derived d1;
    Derived d2;
    std::cout << "Calling d1" << std::endl;
    d1.sampleFunction1();
    d1.sampleFunction2();
    std::cout << "Calling d2" << std::endl;
    d2.sampleFunction2();
    d2.sampleFunction1();
    return 0;
}

输出:请注意,将调用v_init哪个示例函数首先调用,而不是在 ctors 中调用。

Base ctor
Derived ctor
Base ctor
Derived ctor
Calling d1
Derived::v_init
Base::sampleFunction1
Base::sampleFunction2
Calling d2
Derived::v_init
Base::sampleFunction2
Base::sampleFunction1