C ++ Mixin - 初始化期间的动态绑定习语
Posted
技术标签:
【中文标题】C ++ Mixin - 初始化期间的动态绑定习语【英文标题】:C++ Mixin - Dynamic Binding During Initialization idiom 【发布时间】:2020-04-28 11:43:31 【问题描述】:我有一个类层次结构,我想使用多态性来调用正确的成员函数。
在基本层面上这是可行的,但是当我尝试使用 Mixin 类来扩展或更改某些类的功能时遇到了问题。
基本上,我想在构造从 mixin 继承的对象时对成员值进行一些验证。
(我来自python背景,很容易创建改变构造函数行为的mixin。方法解析顺序保证从构造函数调用的函数首先从派生类调用)
在 C++ 中,动态绑定在构造函数中被禁用(我理解原因)。对virtual void init()
函数的调用将不起作用,因为总是会调用基类函数。
有什么方法可以保证validate()
函数的执行不需要再次显式定义构造函数?
工厂是一种方法,但我也希望构造函数具有不同的类型参数。
一个最小的例子如下所示。
谢谢你
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()
【问题讨论】:
【参考方案1】:您可以尝试使用可变参数模板和完美转发来创建您的 Mixin 构造函数,这样您就不必为每个可能的参数数量定义一个版本。
struct B : public A
template<typename... Args>
B(Args&&... args) : A(std::forward<Args>(args)...) this->validate();
;
你有没有想过给 Mixin 一个“验证器”类类型的成员变量,它的构造函数接受一个指向 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::a\n"; return 1;
virtual int b() std::cout << "A::b\n"; return a();
;
struct B : public A
virtual int a() std::cout << "B::a\n"; return 2;
virtual int b() std::cout << "B::b\n"; 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
的调用。
编辑: 如果您希望进一步派生类提供验证功能,如 cmets 中所述,并且您没有 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;
【讨论】:
在你的最后一个例子中:当从B
派生C
并将virtual int b()
添加到它时,C
的构造函数仍将调用B::b()
而不是C::b()
。 (除非将新的构造函数添加到 C
)。这就是我说那句话的意思。其他建议听起来不错,我会看看我是否可以实施它们。您在第二个建议中提到了 C++
再说一次,不是 100% 的意思,但这 godbolt.org/z/S4r4mL 是否显示有用的东西?调用哪个 a() 或 b() 将取决于调用时对象的动态类型,在这种情况下,它将更改 A->B->C,因为对象的部分得到构造。
“内存损失”我只是说 C++ 通常不允许对象大小为 0,因此 v_
将占用 1 个字节,即使它是空的,沿具有填充/对齐含义,所以还不错......大多数时候。
对,对于init()
的讨论,我只是想澄清一下C::init()
将在C 的构造函数运行时被调用——也就是说,如果在构造B 部分之后,并且然后开始构造对象的实际 C 部分。我只是想指出,当构造 C 类型的对象时,对象将经历存在和对象 A、然后是 B、然后是 C 的阶段,因为每个部分的构造函数都会运行,并且虚拟调用将是相应地路由。
至于可变参数模板构造函数的运行时性能,这应该不是问题,应该是最有效的参数转发方式,只要能做出这样的陈述。跨度>
以上是关于C ++ Mixin - 初始化期间的动态绑定习语的主要内容,如果未能解决你的问题,请参考以下文章
为啥事件处理程序只能在 IHttpModule 初始化期间绑定到 HttpApplication 事件?
为啥我应该更喜欢“显式类型的初始化程序”习语而不是显式给出类型