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;
}