Почему unique_ptr и shared_ptr не делают недействительным указатель, из которого они построены? - PullRequest
3 голосов
/ 05 марта 2020

Примечание: это вопрос разработки 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?
}

Ответы [ 2 ]

4 голосов
/ 05 марта 2020

Пока у них обоих есть метод get(), аннулирование исходного указателя не является "более защищенным", но более запутанным подходом.

В том, как необработанные указатели используются в C ++, они не представляйте никакой концепции собственности сами по себе и не определяйте время жизни объекта. Любой, кто использует необработанные указатели в C ++, должен знать, что они действительны только тогда, когда объект существует (независимо от того, обеспечивается ли время жизни объекта интеллектуальными указателями или логами c программы). Создание умного указателя из необработанного указателя не «передает» право собственности на объект, а «назначает» его.

Это различие может быть прояснено в следующем примере:

std::unique_ptr<int> ptr1 = std::make_unique<int>(1);
int* ptr2 = ptr1.get();
// ...
// somewhere later:
std::unique_ptr<int> ptr3(ptr2);
// or std::unique_ptr<int> ptr3(std::move(ptr2));

В этом случае право собственности на объект int не передается ptr3. Он ошибочно назначен ему без освобождения от владения ptr1. Программисту необходимо , чтобы знать, откуда берется указатель, переданный конструктору std::unique_ptr, и как до сих пор обеспечивалось владение объектом. Уверенность библиотеки в том, что она аннулирует эту конкретную переменную-указатель, может дать программисту ложное чувство безопасности, но не обеспечивает реальной безопасности.

1 голос
/ 08 марта 2020

Я думаю, что ответ прост: ноль накладных расходов. Это изменение не требуется для того, чтобы unique_ptr был функциональным, поэтому стандарт не требует этого. Если вы считаете, что это повышает безопасность достаточно, чтобы она того стоила, вы можете попросить свою реализацию добавить ее (возможно, под специальным флагом компиляции).

Кстати, я ожидаю, что анализаторы stati c будут знать достаточно, чтобы предупредить против этого шаблона кода.

...