2012-06-30 4 views
54

Я пытаюсь использовать C++ 11 unique_ptr; Я заменяю полиморфный необработанный указатель внутри моего проекта, который принадлежит одному классу, но довольно часто проходит.Как пройти std :: 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 как ссылки, что бы быть ссылкой на ссылку. Во-вторых, потому что сигнатура функции становится еще больше. В-третьих, потому что в сгенерированном коде для достижения моей переменной потребуется два последовательных указателя указателя.

ответ

69

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

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); 
+1

Это хорошо, но вы можете избавиться от 'std :: unique_ptr' для' 'std :: vector ' аргумента? –

17

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

bool func(BaseClass& base, int other_arg); 

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

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

17

Преимущество использования 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 значений:

  1. Адрес блока памяти, где расположен желаемый объект. (good)
  2. Адрес 0x0, который вы можете быть уверенным, не является неразрешимым и может иметь семантику «ничего» или «нет объекта». (the bad)
  3. Адрес блока памяти, который находится за пределами адресного пространства вашего процесса (разыменование его, мы надеемся, приведет к сбою вашей программы). (уродливый)
  4. Адрес блока памяти, который может быть разыменован, но который не содержит того, что вы ожидаете. Возможно, указатель был случайно изменен, и теперь он указывает на другой доступный для записи адрес (полностью другой переменной в вашем процессе). Запись в это место памяти вызовет массу удовольствия, иногда, во время исполнения, потому что ОС не будет жаловаться, пока вам разрешено писать там. (Zoinks!)

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

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

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

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

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

0

Лично я не беру ссылку на указатель/умный указатель. Потому что что произойдет, если указатель 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 существует одна организация, то накладные расходы std::shared_ptr не окупаются.

+0

Знаете ли вы, что в большинстве случаев «std :: unique_ptr» имеет нулевые служебные данные, не так ли? – lvella

+0

Да, я знаю об этом. Вот почему я рассказал ему о служебных расходах 'std :: shared_ptr'. –

Смежные вопросы