我有一个类的层次结构,我想使用多态性来调用正确的成员函数。在基本层面上,这是可行的,但我在尝试使用Mixin类来扩展或更改某些类的功能时遇到了一个问题。基本上,我想在构造从mixin继承的对象时对成员值进行一些验证。(我来自python背景,在那里很容易创建改变构造函数行为的mixin。方法解析顺序确保从构造函数调用的函数首先从派生类调用(在C++中,构造函数中禁用了动态绑定(我理解原因(。对virtual void init()
函数的调用将不起作用,因为总是调用基类函数。
有没有什么方法可以保证validate()
函数在不需要再次显式定义构造函数的情况下执行?
工厂是一种方法,但我也希望有具有不同类型参数的构造函数。
下面显示了一个最小的示例。
感谢Davence
class Base
{
Base(){};
//... some virtual functions...
}
class Derived: public Base
{
using Base::Base;
// new constructors
Derived(some pars){};
Derived(other pars){};
//... some virtual functions...
}
template <class B>
class Mixin: public B
{
using B::B;
Mixin()
{
// mixin related constructor code
// maybe some member validation or discretization of a continuous value
// hides B::B(), but is only called if the default constructor is called, not for B::B(pars)
this->validate();
}
void validate(){};
}
class UsingMixin: public Mixin<Derived>
{
using Mixin::Mixin; // inherit all constructors
// I want to avoid defining the same constructors from Derived again,
// since there would be no change
// some functions
}
编辑:实现这一点的一种方法是在mixin上使用模板化构造函数,但我不知道这种方法的安全性和可用性,因为我需要知道基类中构造函数参数的最大数量。
template <class B>
class Mixin: public B
{
template <class Arg0>
Mixin(Arg0 arg0)
: B(arg0)
{
this->validate();
}
template <class Arg0, class Arg1>
Mixin(Arg0 arg0, Arg1 arg1)
: B(arg0, arg1)
{
this->validate();
}
template <class Arg0, class Arg1, class Arg2>
Mixin(Arg0 arg0, Arg1 arg1, Arg2 arg2)
: B(arg0, arg1, arg2)
{
this->validate();
}
template <class Arg0, class Arg1, class Arg2, class Arg3>
Mixin(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3)
: B(arg0, arg1, arg2, arg3)
{
this->validate();
}
void validate(){}
}
您可以尝试使用可变模板和完美的转发来创建Mixin构造函数,这样您就不必为每个可能数量的参数定义一个版本。
struct B : public A {
template<typename... Args>
B(Args&&... args) : A(std::forward<Args>(args)...) { this->validate(); }
};
您有没有想过给Mixin一个"validator"类类型的成员变量,该类类型的构造函数获取指向Mixin的指针,并在其上调用validate
?这样,你就不需要为Mixin创建一个构造函数(只需为你的"验证器"成员定义一个默认的初始值设定项(,它将在你的Mixin构造函数所在的时间运行
struct B : public A {
using A::A;
struct V { V(B & b) { b.validate(); } };
V v_ = { *this };
};
用C++20的[[no_unique_address]]
https://en.cppreference.com/w/cpp/language/attributes/no_unique_address您甚至不必为拥有该空成员而支付任何内存惩罚。
方法解析顺序保证从构造函数首先从派生类调用(在C++动态构造函数中禁用了绑定(我理解原因(。一个电话到虚拟void init((函数将不起作用,因为将调用基类函数。
不知道你说的是什么意思。请参见此示例:https://godbolt.org/z/RVSkpi
#include <iostream>
struct A {
virtual int a() { std::cout << "A::an"; return 1; }
virtual int b() { std::cout << "A::bn"; return a(); }
};
struct B : public A {
virtual int a() { std::cout << "B::an"; return 2; }
virtual int b() { std::cout << "B::bn"; return a(); }
B() : a_(b()) { b(); }
int a_;
};
int main() {
B b;
return 0;
}
在执行B
成员的第一个构造函数之前(以及在A
的构造函数完成执行之后(,正在构造的对象"变为"B
类型,并保持这种状态,直到B
的构造函数结束(之后它可能变为继承自B
的其他类型(。在构造函数中,根本不需要虚拟查找,因为编译器知道类型正是B
,并且可以静态地解析方法调用。但对于从b()
调用a()
,它不能做到这一点,因为它不仅可以从构造函数调用。但是,由于在本例中将调用b()
时,对象的动态类型为B
,因此这些类型也将在运行时解析为对B::a
的调用。
编辑:如果你想让一个进一步的派生类提供验证函数,如评论中所述,并且你没有C++20,你可以尝试这样的方法:https://godbolt.org/z/r23xJv
#include <iostream>
struct A {
A(int a) : a_(a) {}
int a_;
};
template<typename T, typename VF>
struct B : T {
using A::A;
struct V { V(B & b) { VF::verify(b); } };
V v_ = { *this };
};
struct C : B<A, C> {
using B::B;
static void verify(B & b) { std::cout << b.a_ << "n"; }
};
int main(int argc, char* argv[]) {
C c(123);
return 0;
}