Примечание: это вопрос разработки API , основанный на дизайне конструкторов unique_ptr
и share_ptr
ради вопроса, но не нацеленный предложить любое изменение их текущих спецификаций.
Хотя обычно рекомендуется использовать make_unique
и make_shared
, и unique_ptr
, и shared_ptr
могут быть созданы из необработанного указателя.
Оба получают указатель по значению и копируют его. Оба позволяют (т.е. в смысле: не препятствуют ) непрерывное использование исходного указателя, переданного им в конструкторе.
Следующий код компилируется и приводит к двойному освобождению:
int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;
И unique_ptr
, и shared_ptr
могут предотвратить вышеперечисленное, если их соответствующие конструкторы ожидают получить необработанный указатель в виде rvalue , например, для unique_ptr:
template<typename T>
class unique_ptr {
T* ptr;
public:
unique_ptr(T*&& p) : ptr{p} {
p = nullptr; // invalidate the original pointer passed
}
// ...
Таким образом, исходный код не будет компилироваться как lvalue , не может связываться с rvalue , но с использованием std::move
код компилируется, будучи более подробным и более защищенным:
int* ptr = new int(9);
std::unique_ptr<int> p { std::move(ptr) };
if (!ptr) {
// we are here, since ptr was invalidated
}
Понятно, что могут быть десятки других ошибок, которые пользователь может сделать с помощью умных указателей. Обычно используемый аргумент : вы должны знать, как правильно использовать инструменты, предоставляемые языком , а C ++ не предназначен для наблюдения за вами et c.
Но, тем не менее, похоже, что была возможность предотвратить эту простую ошибку и поощрить использование make_shared
и make_unique
. И даже до того, как make_unique
было добавлено в C ++ 14, всегда есть возможность прямого выделения без переменной-указателя, например:
auto ptr = std::unique_ptr<int>(new int(7));
Кажется, что запрос rvalue reference указатель в качестве параметра конструктора может добавить немного дополнительной безопасности. Более того, семантика получения rvalue представляется более точной, поскольку мы берем на себя владение переданным указателем.
Переходя к вопросу , почему стандарт не принял это более безопасный подход?
Возможная причина может заключаться в том, что предложенный выше подход предотвратит создание unique_ptr
из константных указателей , то есть следующий код не сможет скомпилироваться с предложенным подходом:
int* const ptr = new int(9);
auto p = std::unique_ptr { std::move(ptr) }; // cannot bind `const rvalue` to `rvalue`
Но, похоже, это редкий сценарий, которым стоит пренебречь, я считаю.
В качестве альтернативы, в случае необходимости поддержки инициализация из константного указателя является сильным аргументом против предложенного подхода, тогда меньший шаг все еще может быть достигнут с помощью:
unique_ptr(T* const&& p) : ptr{p} {
// ...without invalidating p, but still better semantics?
}