Я запутался в поведении, которое я вижу, когда функции копирования и перемещения производного класса вызывают свои версии базового класса.
У меня есть базовый класс с различными конструкторами, которые сообщают мне, когда они называются:
#include <iostream>
class Base {
public:
Base() {}
template<typename T>
Base(T&&) { std::cout << "URef ctor\n"; }
Base(const Base&) { std::cout << "Copy ctor\n"; }
Base(Base& rhs): Base(const_cast<const Base&>(rhs))
{ std::cout << " (from non-const copy ctor)\n"; }
Base(Base&&) { std::cout << "Move ctor\n"; }
Base(const Base&& rhs): Base(rhs)
{ std::cout << " (from const move ctor)\n"; }
};
Для производного класса с созданными компилятором операциями копирования и перемещения
class Derived: public Base {};
и этот тестовый код,
int main()
{
Derived d;
Derived copyNCLValue(d);
Derived copyNCRvalue(std::move(d));
const Derived cd;
Derived copyCLValue(cd);
Derived copyCRvalue(std::move(cd));
}
GCC 4.8.1 производит этот вывод:
Copy ctor
Move ctor
Copy ctor
Copy ctor
Это удивляет меня. Я ожидал, что конструктор базового класса будет вызывать универсальную ссылку, потому что он может быть создан для создания точного совпадения с производным объектом, который предположительно передается из функций производного класса. Функции копирования и перемещения базового класса требуют преобразования в производную базу.
Если я изменю производный класс, чтобы объявить функции копирования и перемещения самостоятельно, но дать им реализацию по умолчанию,
class Derived: public Base {
public:
Derived(){}
Derived(const Derived& rhs) = default;
Derived(Derived&& rhs) = default;
};
GCC выдает тот же результат. Но если я сам напишу функции, используя то, что считаю версией по умолчанию,
class Derived: public Base {
public:
Derived(){}
Derived(const Derived& rhs): Base(rhs) {}
Derived(Derived&& rhs): Base(std::move(rhs)) {}
};
Я получаю вывод, который первоначально ожидал:
URef ctor
URef ctor
URef ctor
URef ctor
Я ожидаю получить одинаковый результат в каждом случае. Это ошибка в gcc, или я чего-то не понимаю?
Это удивляет меня. Я ожидал, что конструктор базового класса будет вызывать универсальную ссылку, потому что он может быть создан для создания точного совпадения с производным объектом, который предположительно передается из функций производного класса. Функции копирования и перемещения базового класса требуют преобразования в производную базу.
Компилятор видит строку Derived copyCRvalue(std::move(cd));
это действительно значит Derived copyCRvalue(static_cast<const Derived&&>(cd));
и он пытается найти конструктор в Derived
это соответствует этому вызову. Он находит два тесно связанных конструктора, оба из которых неявно объявлены:
Derived(Derived const &); // copy constructor
Derived(Derived &&); // move constructor
Второй не может быть использован, так как rvalue-ссылка на const
объект, но первый совпадает. Определение неявно определенного конструктора копирования:
Derived(Derived const &rhs) : base(static_cast<Base const &>(rhs)) {}
Внутри конструктора rhs
является именующий, не rvalue (и шаблонный конструктор в любом случае не является конструктором копирования).
Но если я сам напишу функции, используя то, что считаю версией по умолчанию,
class Derived: public Base {
public:
Derived(){}
Derived(const Derived& rhs): Base(rhs) {}
Derived(Derived&& rhs): Base(std::move(rhs)) {}
};
За исключением того, что это не те определения, которые предоставит компилятор. Конкретная квота из стандарта в 12,8 / 15
Неявно определенный конструктор копирования / перемещения для класса X, не являющегося объединением, выполняет пошаговое копирование / перемещение своих баз и членов.
То есть неявно определенный конструктор будет инициализировать базу назначения с базой источника, и аналогично каждому члену в назначении с тем же членом в источнике.
Других решений пока нет …