想象一下这些课程:
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
还有其他建议吗?
答案 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
,也不能被Base
和BaseHolder
调用。派生类所要做的就是定义一个接受BuildToken
的c'tor并将其转发给基类。但是,派生类无法默认初始化BuildToken
对象本身,它们只能将其复制以转发到其基类上。可以创建令牌的唯一位置是BaseHolder
。这也是唯一在适当时间调用init
的地方。
使用实用程序MakeType
可以使BaseHolder
声明看起来更容易:
BaseHolder item(make<Derived>);
答案 1 :(得分:2)
没有任何公共构造函数,而是具有(朋友)make_base
和make_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都不会在此时初始化。
所以您不能保证正确的预期行为。
请在此处查看有关该问题的更多详细信息: