Подтвердить что ты не робот

Преобразование std:: unique_ptr <Derived> в std:: unique_ptr <Base>

Скажем, у меня есть функции factory, относящиеся к базовому и производному классам:

#include <memory>

using namespace std;

struct B { virtual ~B() {} };
struct D : B {};

unique_ptr<B> MakeB()
{
    auto b = unique_ptr<B>( new B() );
    return b; // Ok!
}

unique_ptr<B> MakeD()
{
    auto d = unique_ptr<D>( new D() );
    return d; // Doh!
}

В последней строке выше мне нужно move(d), чтобы заставить ее работать, иначе я получаю сообщение "Ошибка: неверное преобразование от std::unique_ptr<D> до std::unique_ptr<D>&&". Моя интуиция заявила, что в этом контексте компилятор должен знать, что он мог бы неявно сделать d значение rvalue и переместить его в базовый указатель, но это не так.

Является ли это несоответствием в моих компиляторах (gcc 4.8.1 и VS2012)? Предполагаемая конструкция unique_ptr? Дефект в стандарте?

4b9b3361

Ответ 1

Поведение компилятора корректно. Существует только неявный ход, когда типы являются одинаковыми, поскольку неявное перемещение задается в терминах компилятора, не выполняющего копирование в случаях, когда это фактически разрешено (см. 12.8/31 и 12.8/32).

12.8/31 (копия elision):

в операторе return в функции с типом возвращаемого класса, когда выражение является именем энергонезависимого автоматического объекта (отличным от параметра функции или параметра catch-clause) с тем же самым cv-неквалифицированным типом как возвращаемый тип функции...

12.8/32 (неявный ход):

Когда критерии для выполнения операции копирования выполняются, [...], разрешение перегрузки для выбора конструктора для копии сначала выполняется , как если бы объект был обозначен rvalue.

Ответ 2

С добавлением вызовов std::move в операторах return, этот код работает для меня на Visual Studio 2013:

#include <memory>

using namespace std;

struct B { virtual ~B() {} };
struct D : B {};

unique_ptr<B> MakeB()
{
  auto b = unique_ptr<B>( new B() );
  return std::move( b );  // *** std::move() added here! ***
}

unique_ptr<B> MakeD()
{
  auto d = unique_ptr<D>( new D() );
  return std::move( d );  // *** std::move() added here! ***
}

Также работает

unique_ptr<B> MakeB()
{
  return unique_ptr<B>( new B() );  // *** Returning a temporary! ***
}

unique_ptr<B> MakeD()
{
  return unique_ptr<D>( new D() );  // *** Returning a temporary! ***
}

Конечно, если вы все равно возвращаете std::unique_ptr<B>, почему бы не переместить экземпляр D в std::unique_ptr<B> в первую очередь? Тогда нет никакого преобразования вообще!