Поддерживается ли неявное преобразование из unique_ptr в необработанный указатель?

Я читал эффективное C ++ 3-е издание. На странице 70 автор говорит:

Как и практически все классы интеллектуальных указателей, tr1::shared_ptr и auto_ptr также перегружают операторы разыменования указателя (operator-> и operator*), что позволяет выполнять неявное преобразование в исходные необработанные указатели (...)

Затем он показывает пример с shared_ptr (который в то время был частью tr1) с неявным преобразованием на основе класса с именем Investment:

shared_ptr<Investment> pi1();
bool taxable1 = !(pi1->isTaxFree());
                    ^implicit conversion

shared_ptr<Investment> pi2();
bool taxable2 = !((*pi2).isTaxFree());
                    ^implicit conversion

Что ж, с тех пор я написал несколько тестовых случаев с unique_ptr, и они выдержали испытание.

Я также узнал о unique_ptr поддерживаемых массивах и shared_ptr также собирается (см. примечание). Однако в моем тестировании неявное преобразование не работает для интеллектуальных указателей вокруг массивов.

Пример: я хотел, чтобы это было действительным...

unique_ptr<int[]> test(new int[1]);

(*test)[0] = 5;

но это не так, согласно моему компилятору (Visual C++ 2015 Update 3).

Затем, проведя небольшое исследование, я нашел доказательства того, что неявное преобразование вообще не поддерживается... например, вот это: https://herbsutter.com/2012/06/21/reader-qa-why-dont-modern-smart-pointers-implicitly-convert-to.

На данный момент я в сомнениях. Поддерживается ли это (стандартом)или нет?


Примечание. Книга может быть немного устаревшей по этой теме, поскольку автор также говорит на странице 65, что "нет ничего похожего на auto_ptr или tr1::shared_ptr для динамически размещаемых массивов, даже в TR1".


person Marc.2377    schedule 16.11.2016    source источник
comment
*test[0] = 5; Вы уважаете результат unique_ptr::operator[]. Конечно, это не работает для простого int.   -  person StoryTeller - Unslander Monica    schedule 16.11.2016
comment
@StoryTeller Достаточно честно. Я исправил это, я думаю...   -  person Marc.2377    schedule 16.11.2016
comment
unique_ptr<T[]> не имеет operator*(). unique_ptr<T> имеетoperator*(), а unique_ptr<T[]> вместо этого имеет operator[](). Так что установите значение, просто используйте: test[0] = 5;   -  person David Scarlett    schedule 16.11.2016
comment
Если вас просто интересует массив в std::unique_ptr, вместо этого рассмотрите возможность использования std::unique_ptr<std::vector<int>> или std::unique_ptr<std::array<int, N>>, где N — длина массива.   -  person Ohashi    schedule 16.11.2016
comment
@Ohashi, лучше просто используй std::vector напрямую. Он с радостью сам переместится, когда это необходимо.   -  person StoryTeller - Unslander Monica    schedule 16.11.2016
comment
@Ohashi - какая польза от unique_ptr<vector<int>> по сравнению с vector<int>? Единственное преимущество, о котором я могу думать, заключается в том, что вы можете передавать первый более дешево, если тип значения вектора не разрешает операции перемещения. Просто это кажется очень неловким.   -  person David Scarlett    schedule 16.11.2016
comment
FWIW, Скотт Мейерс (Effective Modern C++) сказал следующее: Существование std::unique_ptr для массивов должно представлять для вас только интеллектуальный интерес, потому что std::array, std::vector и std::string фактически всегда лучший выбор структуры данных, чем необработанные массивы. Единственная ситуация, которую я могу себе представить, когда std::unique_ptr‹T[]› имеет смысл, — это когда вы используете C-подобный API, который возвращает необработанный указатель на массив кучи, владельцем которого вы считаете себя.   -  person David Scarlett    schedule 16.11.2016
comment
@DavidScarlett, вы все равно можете перемещать сам вектор, даже если параметр шаблона нельзя перемещать.   -  person StoryTeller - Unslander Monica    schedule 16.11.2016
comment
@StoryTeller @DavidScarlett Кажется, я перепутал его с std::shared_ptr, потому что думал о совместном использовании одного и того же экземпляра std::vector. Спасибо, что указали на это.   -  person Ohashi    schedule 16.11.2016
comment
@StoryTeller Правильно, извините, я запутался с требованием, чтобы конструктор перемещения типа значения был объявлен nothrow, но это для случая, когда вектор перемещает элементы при изменении размера, а не для перемещения вектора.   -  person David Scarlett    schedule 16.11.2016


Ответы (2)


Вот в чем дело. Нет неявного преобразования в базовый указатель, вам нужно вызвать определенную функцию-член get (это тема в стандартной библиотеке, подумайте std::string::c_str).

Но это хорошо! Неявное преобразование указателя может нарушить гарантии unique_ptr. Рассмотрим следующее:

std::unique_ptr<int> p1(new int);
std::unique_ptr<int> p2(p1);

В приведенном выше коде компилятор может попытаться передать указатель p1s в p2! (Этого не произойдет, поскольку этот вызов все равно будет неоднозначным, но предположим, что это не так). Они оба назовут delete!

Но мы по-прежнему хотим использовать интеллектуальный указатель, как если бы он был необработанным. Следовательно, все операторы перегружены.


Теперь давайте рассмотрим ваш код:

(*test)[0] = 5;

Он вызывает unique_ptr::operator*, который возвращает int&1. Затем вы пытаетесь использовать на нем оператор нижнего индекса. Это ваша ошибка.
Если у вас есть std::unique_ptr<int[]>, просто используйте перегрузку operator[], которую предоставляет дескриптор:

test[0] = 5;

1 Как указал Дэвид Скарлетт, он даже не должен компилироваться. Версия массива не должна иметь этого оператора.

person StoryTeller - Unslander Monica    schedule 16.11.2016
comment
Однако приятно иметь неявное преобразование. Представьте себе реализацию интерфейса времени выполнения, подобного Objective C, для C++. У вас должно быть что-то вроде (NSString::alloc())-›autorelease(), преобразование между NSString и NSObject, NSSpecialString и т. д. требует, чтобы вы прибегали к отчаянным средствам, похожим на QueryInterface от Microsoft IUnknown. Я почти уверен, что с нашим нынешним C++ мы можем просто автоматизировать этот процесс. - person Dmitry; 16.11.2016
comment
На самом деле только однообъектная версия, unique_ptr<T> имеет operator*(). Версия массива unique_ptr<T[]> вместо этого имеет operator[](). Таким образом, вместо того, чтобы возвращать первый элемент, (*test) даже не должен компилироваться. GCC дает: error: no match for 'operator*' (operand type is 'std::unique_ptr<int []>') - person David Scarlett; 16.11.2016
comment
@Dmitry, я согласен, что это приятно, поэтому Андрей Александреску предоставляет этот вариант в качестве политики в Modern C++ Design; с операторами преобразования explicit в большинстве случаев это еще менее подвержено ошибкам. Но стандартная библиотека придерживается более осторожного подхода, к которому я не могу придраться. - person StoryTeller - Unslander Monica; 16.11.2016
comment
@DavidScarlett, хороший улов. Я совсем забыл, что это даже не должно работать. - person StoryTeller - Unslander Monica; 16.11.2016
comment
Прохладный! Я согласен, что неявное преобразование не очень хорошо, и вызов get() не имеет большого значения. Думаю, меня просто смутило то, что говорится в книге и что делает компилятор, по сравнению с тем, что можно найти в Интернете. Спасибо. - person Marc.2377; 16.11.2016
comment
@DavidScarlett, чтобы быть справедливым, версия массива типа является типом структуры/контейнера. Это желаемое поведение. Массивы не сохраняют свою форму при перебрасывании, они превращаются в указатели, если вы не оберните их в структуру. - person Dmitry; 16.11.2016
comment
Что касается примечания ¹, основанного на комментарии @DavidScarlett, оно действительно не компилируется. - person Marc.2377; 16.11.2016

Как указывает StoryTeller, неявные преобразования испортили бы шоу, но я хотел бы предложить другой способ думать об этом:

Интеллектуальные указатели, такие как unique_ptr и shared_ptr, пытаются скрыть базовый необработанный указатель, потому что они пытаются поддерживать над ним определенную семантику владения. Если бы вы могли свободно получить этот указатель и передавать его, вы могли бы легко нарушить эту семантику. Они по-прежнему предоставляют способ доступа к нему (get), так как они не могли бы полностью остановить вас, даже если бы захотели (в конце концов, вы можете просто следовать умному указателю и получить адрес указателя). Но они все еще хотят поставить барьер, чтобы убедиться, что вы не получите к нему доступ случайно.

Однако не все потеряно! Вы все еще можете получить это синтаксическое удобство, определив новый тип интеллектуального указателя с очень слабой семантикой, так что его можно будет безопасно неявно построить из большинства других интеллектуальных указателей. Рассмотреть возможность:

// ipiwdostbtetci_ptr stands for : 
// I promise I wont delete or store this beyond the expression that created it ptr
template<class T>
struct ipiwdostbtetci_ptr {
    T * _ptr;
    T & operator*() {return *_ptr;}
    T * operator->(){return _ptr;}
    ipiwdostbtetci_ptr(T * raw): _ptr{raw} {}
    ipiwdostbtetci_ptr(const std::unique_ptr<T> & unq): _ptr{unq.get()} {}
    ipiwdostbtetci_ptr(const std::shared_ptr<T> & shr): _ptr{shr.get()} {}
};

Итак, в чем смысл этой сатирически названной умной указки? Это просто своего рода указатель, которому устно дается контракт о том, что пользователь никогда не будет оставлять его или его копию в живых за пределами выражения, которое его создало, и пользователь также никогда не будет пытаться его удалить. С этими ограничениями, за которыми следует пользователь (без проверки компилятором), совершенно безопасно неявно преобразовывать многие интеллектуальные указатели, а также любые необработанные указатели.

Теперь вы можете реализовать функции, которые ожидают ipiwdostbtetci_ptr (при условии, что они будут соблюдать семантику), и удобно вызывать их:

void f(ipiwdostbtetci_ptr<MyClass>);
...
std::unique_ptr<MyClass> p = ...
f(p);
person enobayram    schedule 16.11.2016