Почему указатель не может быть автоматически преобразован в unique_ptr при его возврате?

Позвольте мне задать свой вопрос на примере.

#include <memory>

std::unique_ptr<int> get_it() {
        auto p = new int;
        return p;
}

int main() {
        auto up ( get_it() );
        return 0;
}

Это не скомпилируется со следующей ошибкой:

a.cpp:5:9: error: could not convert ‘p’ from ‘int*’ to ‘std::unique_ptr<int>’
  return p;
         ^

Почему здесь нет автоматического преобразования необработанного указателя в уникальный? И что я должен делать вместо этого?

Мотивация: я понимаю, что использование интеллектуальных указателей для ясности владения должно быть хорошей практикой; Я получаю указатель (который у меня есть) откуда-то, как int* в данном случае, и я (думаю, что я) хочу, чтобы он был в unique_ptr.


Если вы планируете прокомментировать или добавить свой собственный ответ, обратитесь по адресу аргументы Герберта Саттера в пользу того, что это возможно в предложении N4029.


person einpoklum    schedule 19.01.2016    source источник
comment
Потому что тогда такие вещи, как int x; return &x;, будут компилироваться.   -  person Kerrek SB    schedule 19.01.2016
comment
@KerrekSB: я также могу сделать int *p = nullptr; *p = 123;, и это скомпилируется. Впрочем, чтобы не слишком спорить - спрошу - что же мне делать-то?   -  person einpoklum    schedule 19.01.2016
comment
Вы делаете один из своего указателя, например. std::unique_ptr<int>(p).   -  person Vincent Savard    schedule 19.01.2016
comment
@VincentSavard: Но разве это не именно то, что я делаю, возвращая p как std::unique_ptr<int>?   -  person einpoklum    schedule 19.01.2016
comment
Нет, вы возвращаете p, который имеет тип int*, а не std::unique_ptr<int>. Возможно, вы не понимаете, что делает auto.   -  person Vincent Savard    schedule 19.01.2016
comment
return std::make_unique<int>(); std::make_unique   -  person Joe    schedule 19.01.2016
comment
@VincentSavard: тип возвращаемого значения — std::unique_ptr<int>, я могу вернуть только его. Я имею в виду, что int * должен быть приведен. Точно так же, как если бы я вернул 3,5 в функции, возвращающей int.   -  person einpoklum    schedule 19.01.2016
comment
@Joe: Предположим, вы не можете избежать p. Каким-то образом создается и устанавливается int* p (волшебным образом, который я не упомянул в MWE).   -  person einpoklum    schedule 19.01.2016
comment
И именно поэтому компилятор не компилирует этот код. Ваш вопрос: Почему p не приводится неявно к std::unique_ptr?   -  person Vincent Savard    schedule 19.01.2016
comment
@VincentSavard: Если бы это было так, я бы не смог скомпилировать int foo() { return 3.5; } и должен был бы сделать int foo() { return (int)3.5;}, но мне это не нужно.   -  person einpoklum    schedule 19.01.2016
comment
Потому что определено неявное преобразование из double в int. Вы смешиваете разные понятия.   -  person Vincent Savard    schedule 19.01.2016
comment
@einpoklum: Тем не менее, это другой уровень неправильности. Ваш p не может быть разыменован, что является вашей ошибкой. Мой &x является вполне допустимым указателем int.   -  person Kerrek SB    schedule 19.01.2016
comment
@cat: не удается воспроизвести   -  person Kerrek SB    schedule 20.01.2016
comment
@cat: также не может воспроизводиться с GCC 5.2.   -  person Kerrek SB    schedule 20.01.2016


Ответы (5)


Ответ двоякий. Все остальные ответы, включая самостоятельный ответ ОП, касались только одной его половины.

Указатель не может быть преобразован автоматически, потому что:

  • unique_ptr конструктор из указателя объявляется explicit, поэтому рассматривается компилятором только в явных контекстах. Это сделано для предотвращения случайных опасных преобразований, когда unique_ptr может захватить указатель и удалить его без ведома программиста. В общем, не только для unique_ptr считается хорошей практикой объявлять все конструкторы с одним аргументом как explicit, чтобы предотвратить случайные преобразования.
  • оператор return считается стандартом неявным контекстом, и поэтому явный конструктор неприменим. Продолжалось обсуждение правильности этого решения, что отражено в вопросе 114 EWG, включая ссылки на несколько предложений: две версии предложений Херба Саттера сделать return явным (N4029, N4074) и два "ответа", утверждающие, что для этого: N4094 Говарда Хиннанта и Вилле Вотилайнена и N4131 Филипа Розена. После нескольких обсуждений и опросов вопрос был закрыт, так как NAD - не дефект.

В настоящее время существует несколько обходных путей:

return std::unique_ptr<int>{p};

or

return std::unique_ptr<int>(p);

В С++ 14 вы также можете использовать автовывод возвращаемого типа функции:

auto get_it() {
    auto p = new int;
    return std::unique_ptr<int>(p);
}

Обновление: добавлена ​​ссылка на вопрос комитета по второму пункту.

person Ilya Popov    schedule 19.01.2016
comment
Благодарю вас за то, что вы обратили мое внимание на предложение г-на Саттера и на обсуждение в ЭРГ, которое является весьма показательным (и я также должен сказать, что оно контрастирует с пренебрежительным отношением некоторых людей). - person einpoklum; 19.01.2016
comment
считается хорошей практикой объявлять все конструкторы с одним аргументом явными, чтобы предотвратить случайные преобразования --- кто считает это хорошей практикой? Сама стандартная библиотека не следует этому правилу. Например, std::shared_ptr‹T› неявно конструируется из std::unique_ptr‹T›. - person Brian Bi; 16.01.2019

Потому что неявное построение unique_ptr из голого указателя было бы очень подвержено ошибкам.

Просто создайте его явно:

std::unique_ptr<int> get_it() {
        auto p = new int;
        return std::unique_ptr<int>(p);
}
person Stas    schedule 19.01.2016
comment
Я не могу сказать, что понимаю, насколько подвержена ошибкам функция, возвращающая unique_ptr. Можете ли вы уточнить немного больше? - person einpoklum; 19.01.2016
comment
Например. foo(unique_ptr<int> p) {} в таком случае сможет украсть переданный голый указатель и уничтожить объект. - person Stas; 19.01.2016
comment
В вашем примере не используется функция, возвращающая unique_ptr, поэтому я не понимаю... - person einpoklum; 19.01.2016
comment
Явность конструкции объекта определяется определением конструктора. Таким образом, вы не можете сделать это неявно при возврате объекта, но явно в других случаях. - person Stas; 19.01.2016
comment
Итак, вы говорите, что это связано с ключевым словом explicit? - person einpoklum; 19.01.2016
comment
Да. unique_ptr конструктор из голого указателя определяется как явный. - person Stas; 19.01.2016
comment
Это хорошо отвечает на вопрос OP - необработанные указатели не могут быть неявно преобразованы в интеллектуальные указатели. - person erip; 19.01.2016

Потому что std::unique_ptr становится владельцем указателя, и вы определенно не хотите случайно получить необработанный указатель deleted.

Если бы это было возможно, то:

void give_me_pointer(std::unique_ptr<int>) { /* whatever */ }

int main() {
    int *my_int = new int;
    give_me_pointer(my_int);
    // my_int is dangling pointer
}
person GingerPlusPlus    schedule 19.01.2016
comment
ИМО не в этом причина. get_it возвращает уникальный указатель, очевидно, что возврат указателя от означает передачу права собственности. - person einpoklum; 19.01.2016
comment
Хороший вопрос, но я не думаю, что С++ достаточно умен, чтобы различать этот случай и случай в вопросе. Итак, оба варианта разрешены или оба запрещены. - person GingerPlusPlus; 19.01.2016
comment
@einpoklum: является действительно причиной. - person ildjarn; 19.01.2016

Поскольку преобразование или приведение требует соответствующего оператора приведения (для исходного типа) или конструктора (для целевого типа). В этом случае должен быть следующий конструктор unique_ptr:

explicit unique_ptr( pointer p );

Который имеет явное ключевое слово. ваш get_it() пытается выполнить неявное преобразование, которое explicit предотвращает. Вместо этого вы должны создать unique_ptr явно, как это было предложено @Stas и @VincentSavard:

std::unique_ptr<int> get_it() {
        auto p = new int;
        return std::unique_ptr<int>(p);
}

или даже, если мы хотим сказать unique_ptr только один раз...

auto get_it() {
        auto p = new int;
        return std::unique_ptr<int>(p);
}
person einpoklum    schedule 19.01.2016
comment
Простите, если глупый вопрос, но почему не просто make_unique? - person erip; 19.01.2016
comment
@erip: Потому что это MWE. На самом деле у нас есть auto p = do_stuff_which_returns_a_raw_pointer() - person einpoklum; 19.01.2016
comment
Да, но вы могли бы просто сделать auto get_it() { auto p = new int; return std::make_unique<int>(p); }, не так ли? Я думаю, что переадресация будет предпочтительнее явного вызова ctor. - person erip; 19.01.2016
comment
@erip: В этом примере да. Но, знаете, я мог бы просто int main() { return 0;}, так как я ничего не делаю с p... - person einpoklum; 19.01.2016
comment
Я думаю, вы упускаете мою мысль. Я отредактировал свой комментарий, чтобы он больше отражал мои намерения. - person erip; 19.01.2016
comment
@erip Это именно то, что предлагает нижняя часть моего ответа. - person einpoklum; 19.01.2016
comment
Эээ... Нет. Есть разница между созданием и пересылкой объекта. - person erip; 19.01.2016
comment
Кроме того, почему вы называете себя ОП? :) Обычно самостоятельные ответы должны добавлять что-то, чего другие ответы не имеют. Я думаю, что ваш ответ покрыт ответом @Stas. - person erip; 19.01.2016
comment
@erip: Я думаю, было бы более запутанно, если бы я написал «О, я могу сделать это вместо этого». Во всяком случае, нет, мой ответ не распространяется на то, что написал Стас; и я не согласен с частью его ответа. - person einpoklum; 19.01.2016
comment
Может быть, нет, но определенно освещал комментарии, которые являются частью ответа @Stas. - person erip; 19.01.2016
comment
@erip: (пожимает плечами) ты тут как бы путаешься. Я написал еще один ответ, потому что хочу выразить то, как я его понял. В любом случае, это не будет принятым ответом. - person einpoklum; 19.01.2016

Потому что это опасно.

Используйте std::make_unique() из С++ 14:

std::unique_ptr<int> get_it()
{
    auto p = std::make_unique<int>();
    return p;
}
person Victor Dyachenko    schedule 19.01.2016