C ++ Mixin - динамическое связывание c во время инициализации - PullRequest
1 голос
/ 28 апреля 2020

У меня есть иерархия классов, для которых я хочу использовать полиморфизм для вызова правильных функций-членов. На базовом уровне c это работает, но я столкнулся с проблемой при попытке использовать класс Mixin для расширения или изменения функциональности некоторого класса. По сути, я хочу провести некоторую проверку значения члена при создании объекта, который наследуется от миксина. (Я пришел из python фона, где легко создавать миксины, которые изменяют поведение конструктора. Порядок разрешения методов гарантирует, что функции, вызываемые из конструктора, сначала будут вызываться из производного класса) В C ++ Dynami c привязка в конструкторах отключено (я понимаю причины). Вызов функции virtual void init() не будет работать, поскольку всегда будет вызываться функция базового класса.

Есть ли способ гарантировать выполнение функции validate() без необходимости явного определения конструкторов снова ?

Фабрики были бы одним методом, но я также хочу иметь конструкторы с различными параметрами типов.

Ниже приведен минимальный пример.

Спасибо за 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
}

РЕДАКТИРОВАТЬ: Одним из способов достижения этого было бы иметь шаблонных конструкторов на 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 Ответ

1 голос
/ 28 апреля 2020

Вы можете попробовать создать свои конструкторы Mixin с помощью шаблонов variadi c и совершенной пересылки, поэтому вам не нужно определять версию для каждого возможного числа аргументов.

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

Задумывались ли вы о дать Mixin переменную-член типа класса «validator», конструктор которого берет указатель на 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 вам даже не придется платить штраф за использование пустого члена.

Порядок разрешения методов гарантирует что функции, вызываемые из конструктора, сначала вызываются из производного класса) В 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, и может разрешать вызовы методов статически. Но он не может сделать это для вызова a() из b(), поскольку он может быть вызван не только из конструктора. Но, поскольку в данный момент будет вызываться b(), тип объекта Dynami c равен B, они также будут преобразованы в вызовы B::a во время выполнения.

РЕДАКТИРОВАТЬ: Если вы хотите, чтобы дополнительный производный класс предоставлял функцию проверки, как упомянуто в комментариях, и у вас нет 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;
}
...