简体   繁体   中英

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. Basically, I want to do some validation on a member value when constructing an object that inherits from the mixin. (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). A call to a virtual void init() function would not work, since always the Base class function would be called.

Is there any way to guarantee that the validate() function is executed without needing to explicitly define the constructors again?

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.


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.

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? 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.

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.

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.

Not sure what you mean by this. See this example: 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 ). 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. But it can't do that for the call to a() from b() , since that can be called not only from the constructor. 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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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