Страницы

Поиск по вопросам

четверг, 12 декабря 2019 г.

Почему shared_ptr запрещает неявное преобразование указателя?

#cpp #cpp11


Этот код не компилируется:

std::shared_ptr pi = new MyType[5];


Вместо этого, так как конструктор shared_ptr объявлен explicit - приходится писать:

std::shared_ptr pi(new MyType[5]);


Я нахожу это как минимум неудобным. 

В чём причины такого дизайна ?

И какой от этого профит ?
    


Ответы

Ответ 1



Допустим у нас есть функция, принимающая умный указатель: void foo(std::shared_ptr data){ //... } Тогда такой код приведет к удалению ресурса, хотя мы этого не подразумевали: MyType *object = new MyType(); foo(object); При вызове функции выполнится создание умного указателя, а при выходе из неё он удалит тот объект которым владел. И все это скомпилируется без каких либо предупреждений.

Ответ 2



explicit нужен для исключения случайных преобразований типов. Например, Ваша функция имеет такой вид: void f(std::shared_ptr p) { ... } И Вы её где-то вызвали так: int i; f(&i); Не будь конструктор explicit, код бы скомпилировался, но Вы бы получили ошибку во время выполнения (в общем случае - неопределённое поведение), т.к. деструктор std::shared_ptr вызовет delete для объекта, расположенного на стеке. Для избежания подобных ситуаций конструктор делается явным explicit. В общем случае, опасно иметь возможность неявно приводить совершенно любой указатель к типу умного (т.е. владеющего ресурсом) указателя, т.к. заранее неизвестно что из себя представляет переданный адрес. Когда программист явно приводит указатель к типу умного, он однозначно сообщает о своих намерениях. Наличие explicit у конструктора класса умного указателя делает это требование обязательным. Причем это актуально для всех типов умных указателей из стандартной библиотеки: unique_ptr, shared_ptr и даже уже устаревший auto_ptr имеют именно explicit конструкторы, принимающие тип обычного указателя.

Ответ 3



Во-первых: в Вашем коде неопределённое поведение, std::shared_ptr не умеет(до C++17) удалять массивы с функцией удаления по умолчанию. Поэтому, внутренний указатель будет удалён с delete, а не delete[]. Во-вторых, идиоматичным методом создания умных указателей в C++ является использование семейства функций make_*. В частности, для std::shared_ptr это std::make_shared; мало того, что это удобнее, но и в случае с std::shared_ptr это ещё и более эффективно(об этом можно почитать в моей статье). Вообще, с появлением умных указателей, new/delete в коде не должно быть, за исключение крайне редких ситуаций, которые, как правило, могут возникнуть при написании собственной библиотеки. Либо же менеджера памяти. Что касается кокретного вопроса почему: позволение неявного преобразования во владеющий указатель создаёт больше проблем, чем решает. Человек должен явно указывать свои намерения по созданию владеющего указателя.

Комментариев нет:

Отправить комментарий