Удивительное поведение в наследовании конструктора нескольких копий - PullRequest
0 голосов
/ 05 июля 2018

Начиная с C ++ 11, может быть два конструктора копирования, один из которых принимает параметр типа T&, а другой - параметр const T&.

У меня есть ситуация, когда (казалось бы) добавление второго конструктора копирования не приводит к вызову ни одного из них , когда конструкторы наследуются в производном классе. Конструктор копирования перекрывается шаблонизированным конструктором, когда оба присутствуют.

Вот MWE:

struct A {
  template <typename... Args>
  A (Args&&... args)
  { std::cout << "non-default ctor called\n"; }

  A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};

struct C :public A { using A::A; };

int main() {
  C c1;
  C c2(c1);
}

Запустив этот код, мы увидим вывод

non-default ctor called
copy ctor from non-const ref

, как и ожидалось.

Однако добавление дополнительного конструктора к struct A выглядит следующим образом:

  A (const A&) { }

каким-то образом заставляет другой конструктор копирования не вызываться, поэтому вывод становится

non-default ctor called
non-default ctor called

В моем случае я хочу наследовать все конструкторы из базового класса в производный класс, включая конструкторы копирования и все остальное. Но, похоже, что два конструктора копирования не наследуются, когда они оба присутствуют. Что здесь происходит?

1 Ответ

0 голосов
/ 05 июля 2018

С https://en.cppreference.com/w/cpp/language/using_declaration

Если один из унаследованных конструкторов Base имеет сигнатуру, совпадающую с конструктором копирования / перемещения производного, это не предотвращает неявную генерацию конструктора производного копирования / перемещения (который затем скрывает унаследованную версию, аналогично использованию оператор =).

So

struct C :public A { using A::A; };

есть

struct C :public A
{
    using A::A;
    C(const C&) = default;
    C(C&&) = default;
};

где C(const C&) = default; аналогично

C(const C& c) : A(static_cast<const A&>(c)) {}

То же самое с

struct A {
  template <typename... Args>
  A (Args&&... args)
  { std::cout << "non-default ctor called\n"; }

  A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};

выбран конструктор шаблона, но

struct A {
  template <typename... Args>
  A (Args&&... args)
  { std::cout << "non-default ctor called\n"; }

  A (const A&) { std::cout << "copy ctor from const ref\n"; }
  A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};

A (const A&) выбрано.

Как вы можете заметить, есть также дефект:

Семантика наследующих конструкторов задним числом изменена в отчете о дефектах в C ++ 11. Ранее объявление наследующего конструктора приводило к тому, что набор синтезированных объявлений конструктора вставлялся в производный класс, что вызывало избыточные копии / перемещения аргументов, имело проблемные взаимодействия с некоторыми формами SFINAE, а в некоторых случаях может быть неосуществимо в основных ABI. Старые компиляторы могут по-прежнему реализовывать предыдущую семантику.

http://www.open -std.org / ОТК1 / SC22 / wg21 / документы / документы / 2015 / p0136r1.html

С этим дефектом ваш класс C будет

struct C :public A
{
    using A::A;

    template <typename ...Ts>
    C(Ts&&... ts) : A(std::forward<Ts>(ts)...) {} // Inherited.

    C(const C&) = default;
    C(C&&) = default;
};

Итак, вы звоните C(C& c) : A(c) {} (после замены шаблона).

...