Как пройти std:: unique_ptr?

У меня есть первая попытка использовать С++ 11 unique_ptr; Я заменяю полиморфный необработанный указатель внутри моего проекта, который принадлежит одному классу, но довольно часто проходит.

У меня были такие функции, как:

bool func(BaseClass* ptr, int other_arg) {
  bool val;
  // plain ordinary function that does something...
  return val;
}

Но вскоре я понял, что не могу переключиться на:

bool func(std::unique_ptr<BaseClass> ptr, int other_arg);

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

Я, хотя передаю указатель как ссылку, вот так:

bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);

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

Ответ 1

Если вы хотите, чтобы функция использовала pointee, передайте ссылку на нее. Нет никакой причины привязать функцию к работе только с каким-то умным указателем:

bool func(BaseClass& base, int other_arg);

И на сайте вызова используйте operator*:

func(*some_unique_ptr, 42);

В качестве альтернативы, если аргументу base разрешено быть нулевым, сохраняйте подпись как есть и используйте функцию члена get():

bool func(BaseClass* base, int other_arg);
func(some_unique_ptr.get(), 42);

Ответ 2

Преимущество использования std::unique_ptr<T> (кроме того, что не нужно запоминать вызовы delete или delete[] явно) заключается в том, что он гарантирует, что указатель либо nullptr, либо указывает на действительный экземпляр ( базовый). Я вернусь к этому после ответа на ваш вопрос, но первое сообщение DO использует интеллектуальные указатели для управления временем жизни динамически выделенных объектов.

Теперь ваша проблема заключается в том, как использовать это со старым кодом.

Мое предложение состоит в том, что если вы не хотите передавать или передавать права собственности, вы должны всегда передавать ссылки на объект. Объявите свою функцию следующим образом (с или без const квалификаторов, если необходимо):

bool func(BaseClass& ref, int other_arg) { ... }

Затем вызывающий, который имеет std::shared_ptr<BaseClass> ptr, либо обрабатывает случай nullptr, либо запрашивает bool func(...) для вычисления результата:

if (ptr) {
  result = func(*ptr, some_int);
} else {
  /* the object was, for some reason, either not created or destroyed */
}

Это означает, что любой вызывающий должен обещать, что ссылка действительна и что она будет оставаться действительной во время выполнения тела функции.


Вот почему я твердо верю, что вы должны не передавать исходные указатели или ссылки на интеллектуальные указатели.

Необработанный указатель - это только адрес памяти. Может иметь одно из (по крайней мере) 4 значений:

  • Адрес блока памяти, в котором находится желаемый объект. (хороший)
  • Адрес 0x0, который вы можете быть уверенным, не является неразрешимым и может иметь семантику "ничего" или "нет объекта". (плохой)
  • Адрес блока памяти, который находится за пределами адресного пространства вашего процесса (разыменование его, мы надеемся, приведет к сбою вашей программы). (уродливый)
  • Адрес блока памяти, который может быть разыменован, но который не содержит того, что вы ожидаете. Возможно, указатель был случайно изменен, и теперь он указывает на другой доступный для записи адрес (полностью другой переменной в вашем процессе). Запись в это место памяти вызовет массу удовольствия, иногда, во время исполнения, потому что ОС не будет жаловаться, пока вам разрешено писать там. (Zoinks!)

Правильное использование интеллектуальных указателей облегчает довольно страшные случаи 3 и 4, которые обычно не обнаруживаются во время компиляции и которые вы обычно испытываете только во время выполнения, когда ваша программа вылетает или неожиданно происходит.

Передача интеллектуальных указателей в качестве аргументов имеет два недостатка: вы не можете изменить const -ность заостренного объекта без создания копии (что добавляет служебные данные для shared_ptr и не возможно для unique_ptr), и вы все еще осталось со вторым значением (nullptr).

Я отметил второй случай как (плохой) с точки зрения дизайна. Это более тонкий аргумент в отношении ответственности.

Представьте, что это означает, когда функция получает nullptr в качестве своего параметра. Сначала нужно решить, что с ним делать: использовать "магическое" значение вместо недостающего объекта? полностью изменить поведение и вычислить что-то еще (что не требует объекта)? паника и исключение? Более того, что происходит, когда функция принимает 2, или 3 или даже больше аргументов с помощью необработанного указателя? Он должен проверять каждый из них и соответствующим образом адаптировать свое поведение. Это добавляет совершенно новый уровень поверх проверки ввода без какой-либо реальной причины.

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

Ответ 3

Я согласен с Martinho, но я думаю, что важно указать семантику собственности pass-by-reference. Я думаю, что правильное решение состоит в том, чтобы использовать простую ссылку на ссылку здесь:

bool func(BaseClass& base, int other_arg);

Общепринятое значение pass-by-reference в С++ похоже на то, что вызывающая функция функции сообщает функции "здесь, вы можете одолжить этот объект, использовать его и изменить его (если не const), но только на время функционирования тела функции". Это никоим образом не противоречит правилам владения unique_ptr, потому что объект просто заимствуется на короткий период времени, фактическая передача собственности не происходит (если вы одалживаете свою машину кому-то, подписать титул ему?).

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

Ответ 4

Лично я избегаю ссылки на указатель/умный указатель. Потому что что происходит, если указатель nullptr? Если вы измените подпись на это:

bool func(BaseClass& base, int other_arg);

Возможно, вам придется защитить свой код от нулевых указаний указателя:

if (the_unique_ptr)
  func(*the_unique_ptr, 10);

Если класс является единственным владельцем указателя, второй вариант Martinho выглядит более разумным:

func(the_unique_ptr.get(), 10);

В качестве альтернативы вы можете использовать std::shared_ptr. Однако, если для delete есть один объект, ответственный за delete, служебные данные std::shared_ptr не окупаются.