Как передать указатель const объекту const с помощью unique_ptr

Я хочу передать unique_ptr вспомогательной функции, и я хочу убедиться, что вспомогательная функция не изменяет указатель и не заостренный объект. Без unique_ptr решение должно иметь

void takePtr(AClass const * const aPtr) {
  // Do something with *aPtr. 
  // We cannot change aPtr, not *aPtr. 
}

(Ну, технически, AClass const * aPtr достаточно.) И я могу назвать это с помощью

AClass * aPtr2 = new AClass(3);
takePtr(aPtr2);

Я хочу вместо этого использовать unique_ptr, но не могу понять, как это записать. Я попробовал

void takeUniquePtr(unique_ptr<AClass const> const & aPtr) {
  // Access *aPtr, but should not be able to change aPtr, or *aPtr. 
}

Когда я вызываю это с помощью

unique_ptr<AClass> aPtr(new AClass(3));
takeUniquePtr(aPtr);

он не компилируется. Ошибка, которую я вижу, это

testcpp/hello_world.cpp:141:21: error: invalid user-defined conversion from ‘std::unique_ptr<AClass>’ to ‘const std::unique_ptr<const AClass>&’ [-fpermissive]

Не следует ли автоматическое преобразование из unique_ptr<AClass> в unique_ptr<AClass const>? Что мне здесь не хватает?

Кстати, если я изменяю unique_ptr<AClass const> const & aPtr на unique_ptr<AClass> const & aPtr в определении функции, он компилируется, но затем я могу вызывать функции типа aPtr->changeAClass(), которые я не хочу разрешать.

Ответы

Ответ 1

Умные указатели предназначены для управления собственностью и продолжительностью жизни, они позволяют нам (среди прочего) безопасно передавать права собственности на различные части нашего кода.

Когда вы передаете const unique_ptr<T>& функции (не имеет значения, является ли T const или нет), то на самом деле это означает, что функция promises никогда не будет изменять сам unique_ptr (но это все еще может изменить объект с указателем, если T не const), т.е. не будет никакой возможной передачи прав собственности. Вы просто используете unique_ptr как бесполезную оболочку вокруг голого указателя.

Итак, как предложил @MarshallClow в комментарии, вы должны просто избавиться от обертки и передать либо голый указатель, либо прямую ссылку. Что классно с этим, так это то, что ваш код теперь семантически прозрачен (ваша подпись функции четко заявляет, что она не путается с собственностью, которая была не сразу же очевидна с помощью const unique_ptr<...>&), и он одновременно решает проблему с "констатированием".

А именно:

void someFunction(const AClass* p) { ... }

std::unique_ptr<AClass> ptr(new AClass());
someFunction(ptr.get());

Изменить:, чтобы ответить на ваш вторичный вопрос: "Почему компилятор не позволяет мне... отбрасывать unique_ptr<A> в unique_ptr<A const>?".

На самом деле вы можете переместить a unique_ptr<A> в unique_ptr<A const>:

std::unique_ptr<A> p(new A());
std::unique_ptr<const A> q(std::move(p));

Но, как вы видите, это означает передачу права собственности с p на q.

Проблема с вашим кодом заключается в том, что вы передаете функцию (ссылку на) unique_ptr<const A> в функцию. Поскольку существует несоответствие типа с unique_ptr<A>, чтобы заставить его работать, компилятору необходимо создать временную копию. Но если вы не передадите права собственности вручную, используя std::move, компилятор попытается скопировать ваш unique_ptr, и он не сможет этого сделать, поскольку unique_ptr явно запрещает его.

Обратите внимание, как проблема исчезает, если вы перемещаете unique_ptr:

void test(const std::unique_ptr<const int>& p) { ... }

std::unique_ptr<int> p(new int(3));
test(std::move(p));

Теперь компилятор может создать временный unique_ptr<const A> и переместить исходный unique_ptr<A>, не нарушая ваших ожиданий (поскольку теперь ясно, что вы хотите переместить, а не копировать).

Итак, корень проблемы состоит в том, что unique_ptr имеет только семантику перемещения, а не семантику копирования, но вам понадобится семантика копии для создания временного и все же сохранить право собственности впоследствии. Яйцо и цыпленок, unique_ptr просто не разработан таким образом.

Если вы теперь рассматриваете shared_ptr, в котором имеет семантику копирования, проблема также исчезает.

void test(const std::shared_ptr<const int>& p) { ... }

std::shared_ptr<int> p(new int(3));
test(p);
//^^^^^ Works!

Причина в том, что компилятор теперь может создать временную std::shared_ptr<const int> копию (автоматически кастинг из std::shared_ptr<int>) и привязать эту временную ссылку к const.

Я предполагаю, что это более или менее охватывает его, хотя мое объяснение не имеет стандартного жаргона и, возможно, не так ясно, как должно быть.:)

Ответ 2

Получил этот старый вопрос о сотовых умных указателях. Вышеуказанные ответы игнорируют простое решение шаблона.

Самый простой вариант шаблона (опция a)

template<class T>
void foo(const unique_ptr<T>& ptr) {
    // do something with ptr
}

это решение позволяет отправлять все возможные опции unique_ptr в foo:

  • const unique_ptr<const int>
  • unique_ptr<const int>
  • const unique_ptr<int>
  • unique_ptr<int>

Если вы специально хотите избежать по некоторым причинам 3 и 4 выше, добавьте const в T:

Принять только const/not-const unique_ptr в const! (вариант b)

template<class T>
void foo(const unique_ptr<const T>& ptr) {
    // do something with ptr
}

Боковое примечание 1

Вы можете перегружать опции "a" и "вариант b", если вы получаете другое поведение для случаев, когда вы можете или не можете изменить указанное значение.

Боковое примечание 2

Если вы не хотите вносить какие-либо изменения в указанное значение в этой функции (никогда! независимо от того, какой тип параметра мы получаем!) - не перегружать.
С помощью опции "b" компилятор не позволит изменить значение, на которое мы указываем. Работа выполнена!
Если вы хотите поддерживать все 4 случая, то есть "вариант a", функция может "случайно" изменить значение, на которое мы указываем, например.

template<class T>
void foo(const unique_ptr<T>& ptr) {
    *ptr = 3;
}

однако это не должно быть проблемой, если хотя бы один вызывающий имеет T, который фактически является const, компилятору это не понравится в этом случае и поможет вам решить эту проблему.
Вы можете добавить такого вызывающего в unit-test, что-то вроде:

    foo(make_unique<const int>(7)); // if this line doesn't compile someone
                                    // is changing value inside foo which is
                                    // not allowed!
                                    // do not delete the test, fix foo!

Фрагмент кода: http://coliru.stacked-crooked.com/a/a36795cdf305d4c7