简体   繁体   English

C++ Mixin - 初始化过程中的动态绑定习语

[英]C++ Mixin - Dynamic Binding During Initialization idiom

I have a hierarchy of classes for which I want to use polymorphism to call the correct member functions.我有一个类层次结构,我想使用多态性来调用正确的成员函数。 On a basic level this works, but I encountered a problem when trying to use a Mixin class to extend or change the functionality of some class.在基本层面上这是可行的,但我在尝试使用 Mixin class 扩展或更改某些 class 的功能时遇到了问题。 Basically, I want to do some validation on a member value when constructing an object that inherits from the mixin.基本上,我想在构造从 mixin 继承的 object 时对成员值进行一些验证。 (I am coming from a python background, where it is easy to create mixins that alter the constructor behaviour. 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). (我来自 python 背景,在那里很容易创建改变构造函数行为的混合。方法解析顺序保证从构造函数调用的函数首先从派生的 class 调用)在 ZF6F87C9FDCF8B3C3F07F93F1EE8712 构造函数中禁用动态绑定我明白原因)。 A call to a virtual void init() function would not work, since always the Base class function would be called.调用virtual void init() function 将不起作用,因为总是会调用 Base class function。

Is there any way to guarantee that the validate() function is executed without needing to explicitly define the constructors again?有什么方法可以保证执行validate() function 而无需再次显式定义构造函数?

Factories would be one method, but I also want to have constructors with different type parameters.工厂是一种方法,但我也希望构造函数具有不同的类型参数。

A minimal example is shown below.下面显示了一个最小的示例。

Thanks in davance非常感谢

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
}

EDIT: One way to achieve this would be to have templated constructors on the mixin, but I don't know about the safety and usability of this approach, since I would need to know about the maximum number of constructors parameters from the base class.编辑:实现这一点的一种方法是在 mixin 上使用模板构造函数,但我不知道这种方法的安全性和可用性,因为我需要知道来自基本 class 的构造函数参数的最大数量。


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(){}
}

You could try creating your Mixin constructors with variadic templates and perfect forwarding, so you don't have to define a version for each possible number of arguments.您可以尝试使用可变参数模板和完美转发创建您的 Mixin 构造函数,因此您不必为每个可能的 arguments 数量定义一个版本。

struct B : public A {
    template<typename... Args>
    B(Args&&... args) : A(std::forward<Args>(args)...) { this->validate(); }
};

Have you thought about giving Mixin a member variable of a "validator" class type whose constructor takes a pointer to the Mixin and just calls validate on it?你有没有想过给 Mixin 一个“验证器”class 类型的成员变量,它的构造函数接受一个指向 Mixin 的指针并只调用validate That way, you don't need to create a constructor for Mixin (just define a default initializer for your "validator" member), and it will be run at exactly the time your Mixin constructor would be.这样,您不需要为 Mixin 创建一个构造函数(只需为您的“验证器”成员定义一个默认初始化程序),它将在您的 Mixin 构造函数的确切时间运行。

struct B : public A {
    using A::A;
    struct V { V(B & b) { b.validate(); } };
    V v_ = { *this };
};

With C++20's [[no_unique_address]] https://en.cppreference.com/w/cpp/language/attributes/no_unique_address you won't even have to pay any memory penalty for having that empty member.使用 C++20 的[[no_unique_address]] https://en.cppreference.com/w/cpp/language/attributes/no_unique_address ,您甚至不必为拥有该空成员支付任何 memory 罚款。

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).方法解析顺序保证从构造函数调用的函数首先从派生的 class 调用)在 C++ 中动态绑定在构造函数中被禁用(我理解原因)。 A call to a virtual void init() function would not work, since always the Base class function would be called.调用虚拟 void init() function 将不起作用,因为总是会调用 Base class function。

Not sure what you mean by this.不知道你的意思是什么。 See this example: https://godbolt.org/z/RVSkpi请参阅此示例: 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;
}

Before the first constructor of members of B is executed (and after the constructor of A has finished executing) the object being constructed "becomes" of type B , and remains that way until the end of the constructor of B (after which it may become some other type that inherits from B ).在执行B的成员的第一个构造函数之前(以及在A的构造函数完成执行之后),正在构造的 object “变成” B类型,并且一直保持这种状态直到B的构造函数结束(之后它可能变成从B继承的其他类型)。 In the constructor, virtual lookup would be simply not needed, since the compiler knows that the type is exactly B , and can resolve method calls statically.在构造函数中,根本不需要虚拟查找,因为编译器知道类型正是B ,并且可以静态解析方法调用。 But it can't do that for the call to a() from b() , since that can be called not only from the constructor.但它不能从b() a()因为它不仅可以从构造函数调用。 But, since at the time b() will be called in the example, the dynamic type of the object is B , those will also be resolved to calls to B::a at runtime.但是,由于在示例中将调用b()时,object 的动态类型是B ,这些也将在运行时解析为对B::a的调用。

EDIT: If you want to have a further derived class supply the verification function, as mentioned in the comments, and you don't have C++20, you could try something like this: https://godbolt.org/z/r23xJv编辑:如果您想要进一步派生的 class 提供验证 function,如评论中所述,并且您没有 C++20,您可以尝试这样的事情:Z5E056C500A1C4B6A7110BADEboltorg8 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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM