C++ Mixin——初始化期间的动态绑定惯用语
C++ Mixin - Dynamic Binding During Initialization idiom
我有一个 classes 层次结构,我想为其使用多态性来调用正确的成员函数。
在基本层面上这是可行的,但我在尝试使用 Mixin class 扩展或更改某些 class 的功能时遇到了问题。
基本上,我想在构造从 mixin 继承的对象时对成员值进行一些验证。
(我来自 python 背景,在那里很容易创建改变构造函数行为的混合。方法解析顺序保证从构造函数调用的函数首先从派生 class 调用)
在 C++ 中,动态绑定在构造函数中被禁用(我理解原因)。调用 virtual void init()
函数将不起作用,因为始终会调用 Base class 函数。
有什么方法可以保证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 上使用模板化构造函数,但我不知道这种方法的安全性和可用性,因为我需要知道来自基础 [=27= 的构造函数参数的最大数量].
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" class 类型的成员变量,它的构造函数接受一个指向 Mixin 的指针并只调用 validate
?这样,您不需要为 Mixin 创建一个构造函数(只需为您的 "validator" 成员定义一个默认初始化程序),并且它将在您的 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,您甚至不必为空成员支付任何内存损失。
The method resolution order guarantees that functions called from the
constructor are called from the derived class first) In C++ dynamic
binding is disabled in constructors (I understand the reasons). A call
to a virtual void init() function would not work, since always the
Base class function would be called.
不确定你的意思。看这个例子: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
的构造函数执行完毕之后)正在构造 "becomes" 类型 B
的对象,并一直保持这种状态直到 B
的构造函数结束(之后它可能会变成从 B
继承的其他类型)。在构造函数中,根本不需要虚拟查找,因为编译器 知道 类型正好是 B
,并且可以静态解析方法调用。但是对于从 b()
到 a()
的调用,它不能这样做,因为它不仅可以从构造函数调用。但是,由于当时 b()
将在示例中调用,对象的动态类型为 B
,这些也将解析为在 运行 处调用 B::a
时间。
编辑:
如果你想进一步派生 class 提供验证功能,如评论中所述,并且你没有 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;
}
我有一个 classes 层次结构,我想为其使用多态性来调用正确的成员函数。
在基本层面上这是可行的,但我在尝试使用 Mixin class 扩展或更改某些 class 的功能时遇到了问题。
基本上,我想在构造从 mixin 继承的对象时对成员值进行一些验证。
(我来自 python 背景,在那里很容易创建改变构造函数行为的混合。方法解析顺序保证从构造函数调用的函数首先从派生 class 调用)
在 C++ 中,动态绑定在构造函数中被禁用(我理解原因)。调用 virtual void init()
函数将不起作用,因为始终会调用 Base class 函数。
有什么方法可以保证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 上使用模板化构造函数,但我不知道这种方法的安全性和可用性,因为我需要知道来自基础 [=27= 的构造函数参数的最大数量].
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" class 类型的成员变量,它的构造函数接受一个指向 Mixin 的指针并只调用 validate
?这样,您不需要为 Mixin 创建一个构造函数(只需为您的 "validator" 成员定义一个默认初始化程序),并且它将在您的 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,您甚至不必为空成员支付任何内存损失。
The method resolution order guarantees that functions called from the constructor are called from the derived class first) In C++ dynamic binding is disabled in constructors (I understand the reasons). A call to a virtual void init() function would not work, since always the Base class function would be called.
不确定你的意思。看这个例子: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
的构造函数执行完毕之后)正在构造 "becomes" 类型 B
的对象,并一直保持这种状态直到 B
的构造函数结束(之后它可能会变成从 B
继承的其他类型)。在构造函数中,根本不需要虚拟查找,因为编译器 知道 类型正好是 B
,并且可以静态解析方法调用。但是对于从 b()
到 a()
的调用,它不能这样做,因为它不仅可以从构造函数调用。但是,由于当时 b()
将在示例中调用,对象的动态类型为 B
,这些也将解析为在 运行 处调用 B::a
时间。
编辑: 如果你想进一步派生 class 提供验证功能,如评论中所述,并且你没有 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;
}