C++Mixin-初始化期间的动态绑定习惯用法



我有一个类的层次结构,我想使用多态性来调用正确的成员函数。在基本层面上,这是可行的,但我在尝试使用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;
}

最新更新