Возвращение local unique_ptr как shared_ptr

Я использовал, чтобы не использовать std::move при возврате std::unique_ptr, потому что это запрещает RVO. У меня есть этот случай, когда у меня есть локальный std::unique_ptr, но возвращаемый тип - std::shared_ptr. Здесь образец кода:

shared_ptr<int> getInt1() {
    auto i = make_unique<int>();

    *i = 1;

    return i;
}

shared_ptr<int> getInt2() {
    return make_unique<int>(2);
}

unique_ptr<int> getInt3() {
    auto ptr = make_unique<int>(2);

    return ptr;
}

int main() {
    cout << *getInt1() << endl << *getInt2() << *getInt3() << endl;
    return 0;
}

GCC принимает оба случая, но Кланг отказывается от getInt1() С помощью этой ошибки:

main.cpp:10:13: error: no viable conversion from 'std::unique_ptr<int, std::default_delete<int> >' to 'shared_ptr<int>'
    return i;
           ^

Здесь оба случая на coliru: GCC, Clang

Оба компилятора принимают третий случай.

Какая ошибка? Спасибо.

Ответы

Ответ 1

Правильный ответ зависит от того, какой стандарт С++ вы говорите.

Если мы говорим о С++ 11, clang корректен (требуется явный переход). Если мы говорим о С++ 14, gcc корректно (явное перемещение не требуется).

С++ 11 говорит в N3290/[class.copy]/p32:

Когда критерии для исключения операции копирования выполняются или будут сэкономленные за тот факт, что исходный объект является параметром функции, и подлежащий копированию объект обозначается значением lvalue, перегрузкой разрешение для выбора конструктора для копии сначала выполняется как будто объект был обозначен rvalue. Если разрешение перегрузки не удается,...

Это требует, чтобы вы получили неявное перемещение, когда выражение возврата имеет тот же тип, что и тип возвращаемой функции.

Но CWG 1579 изменил это, и этот отчет о дефекте был принят после С++ 11, а также для С++ 14. Этот же параграф теперь гласит:

Когда критерии для выполнения операции копирования/перемещения выполняются, но не для объявления исключения, и объект, который нужно скопировать, обозначается lvalue, или когда выражение в выражении returnявляется (возможно, в скобках) id-выражением, которое называет объект с время автоматического хранения, указанное в теле или параметр-декларация-предложение самой внутренней закрывающей функции или lambda-expression, разрешение перегрузки, чтобы выбрать конструктор для копия сначала выполняется так, как если бы объект был назначен Rvalue. Если первое разрешение перегрузки выходит из строя или не выполнялось,...

Эта модификация в основном позволяет преобразовать тип возвращаемого выражения в тип возвращаемой функции и по-прежнему иметь право на неявное перемещение.

Означает ли это, что для кода нужен #if/#else на основе значения __cplusplus?

Можно было это сделать, но я бы не стал беспокоиться. Если бы я нацелился на С++ 14, я бы просто:

return i;

Если код неожиданно запускается под компилятором С++ 11, вы будете уведомлены во время компиляции ошибки, и это тривиально исправить:

return std::move(i);

Если вы просто нацеливаете С++ 11, используйте move.

Если вы хотите настроить таргетинг как на С++ 11, так и на С++ 14 (и далее), используйте move. Недостатком использования move является то, что вы можете блокировать RVO (Оптимизация возвращаемого значения). Однако в этом случае RVO даже не является законным (из-за преобразования из оператора return в возвращаемый тип функции). И поэтому безвозмездное move ничего не болит.

В один прекрасный момент вы можете отказаться от бесплатного move, даже если таргетинг на С++ 14, если без него, все еще компилируется на С++ 11 и вызывает дорогостоящее преобразование копии, в отличие от преобразования перемещения. В этом случае случайная компиляция под С++ 11 приведет к молчанию производительности. И когда скомпилировано под С++ 14, безвозмездное move все еще не имеет пагубных последствий.

Ответ 2

std::unique_ptr может быть использован для построения std::shared_ptr только тогда, когда это значение r. См. Объявление конструктора std::shared_ptr:

template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r );

Итак, вам нужно использовать std::move, чтобы сделать работу 1-го случая, иначе он должен потерпеть неудачу.

return std::move(i);

BTW: я скомпилировал код с gcc 4.9.3, и он тоже не прошел.

source_file.cpp:14:12: error: cannot bind ‘std::unique_ptr<int, std::default_delete<int> >’ 
lvalue to ‘std::unique_ptr<int, std::default_delete<int> >&&’
     return i;
            ^