Что является предпочтительным способом передачи указателя/ссылки на существующий объект в конструкторе?
Я начну с примера. В boost есть класс "токенизатор". Он принимает строку, которая должна быть обозначена как параметр в конструкторе:
std::string string_to_tokenize("a bb ccc ddd 0");
boost::tokenizer<boost::char_separator<char> > my_tok(string_to_tokenize);
/* do something with my_tok */
Строка не модифицируется в токенизаторе, поэтому она передается ссылкой на объект const. Поэтому я могу передать туда временный объект:
boost::tokenizer<boost::char_separator<char> > my_tok(std::string("a bb ccc ddd 0"));
/* do something with my_tok */
Все выглядит отлично, но если я попытаюсь использовать токенизатор, произойдет катастрофа. После короткого расследования я понял, что класс токенизатора хранит ссылку, которую я ему дал, и использую для дальнейшего использования. Конечно, он не может работать хорошо для ссылки на временный объект.
В документации не указано явно, что объект, переданный в конструкторе, будет использоваться позже, но в порядке, также не указано, что это не будет:) Поэтому я не могу предположить это, моя ошибка.
Это немного запутанно. В общем случае, когда один объект берет другую по ссылке const, это предполагает, что там может быть предоставлен временный объект. Как вы думаете? Это плохое соглашение? Может быть, в таких случаях следует использовать указатель на объект (а не ссылку)? Или еще дальше - не будет ли полезно иметь какое-то специальное ключевое слово для аргумента, разрешающего/запрещающего предоставление временного объекта в качестве параметра?
EDIT: документация (версия 1.49) довольно минималистична, и единственная часть, которая может предложить такую проблему:
Примечание: при построении фактически не выполняется синтаксический анализ. Анализ выполняется по требованию, так как маркеры доступны через итератор, предоставленный в начале.
Но он явно не указывает, что будет использоваться тот же объект, который был указан.
Однако в этом вопросе речь идет скорее о стиле кодирования, это только пример, который вдохновил меня.
Ответы
Ответ 1
Если какая-либо функция (например, конструктор) принимает аргумент как reference-to-const, то она должна либо
- Удостоверьтесь, что срок действия объекта, на который ссылается, должен удовлетворять определенным требованиям (например, "Не уничтожается до этого и что происходит" )
или
- Создавайте копии внутри, если необходимо использовать данный объект в более поздней точке.
В этом конкретном случае (класс boost::tokenizer
) я бы предположил, что последнее не выполняется по соображениям производительности и/или чтобы сделать класс пригодным для использования с типами контейнеров, которые в первую очередь не могут быть скопированы. По этой причине я считаю это ошибкой документации.
Ответ 2
Лично я думаю, что это плохая идея, и лучше написать конструктор либо для копирования строки, либо вместо const std::string*
. Это только один дополнительный символ для вызывающего типа, но этот персонаж случайно останавливает их с помощью временного.
Как правило: не создавайте ответственности за людей для поддержания объектов, не делая очевидным, что они несут ответственность.
Я думаю, что специальное ключевое слово не будет достаточно полным решением, чтобы оправдать изменение языка. Это не фактически временные проблемы, это проблема, это любой объект, который живет за меньшее время, чем объект, который строится. В некоторых случаях временное было бы хорошо (например, если сам объект tokenizer
также был временным в одном и том же полном выражении). Я действительно не хочу общаться с языком ради половины исправления, и есть более полные исправления (например, возьмите shared_ptr
, хотя у него есть свои проблемы).
"Поэтому я не могу этого допустить, моя ошибка"
Я не думаю, что это действительно ваша ошибка, я согласен с Фририхом в том, что так же, как и против моего личного руководства по стилю, чтобы сделать это вообще, если вы это сделаете и не документируете тогда, что ошибка документации в любом разумный стиль руководства.
Это абсолютно необходимо, чтобы требуемая продолжительность жизни по ссылке параметров функции документирована, если она что-нибудь другое, чем "по крайней мере, до тех пор, как вызов функции". Это то, что docs часто слабо, и должно быть сделано правильно, чтобы избежать ошибок.
Даже в собранных мусором языках, где само время жизни автоматически обрабатывается и, как правило, игнорируется, имеет значение, можете ли вы изменить или повторно использовать свой объект, не изменяя поведение какого-либо другого объекта, который вы передали ему метод, некоторое время в прошлом. Поэтому функции должны документировать, сохраняют ли они псевдоним своих аргументов на любом языке, который не имеет ссылочной прозрачности. Тем более, что в С++, где время жизни объекта является проблемой вызывающего абонента.
К сожалению, единственный механизм, который фактически гарантирует, что ваша функция не сможет сохранить ссылку, - это передать значение, которое имеет стоимость исполнения. Если вы можете придумать язык, который обычно позволяет наложение псевдонимов, но также имеет свойство стиля restrict
C-стиля, которое принудительно применяется во время компиляции const-style, чтобы предотвратить функционирование от белки ссылок на их аргументы, тогда удачи и знака меня.
Ответ 3
Как говорили другие, пример boost::tokenizer
является результатом либо ошибки в tokenizer
, либо предупреждения, отсутствующего в документации.
Чтобы ответить на вопрос, я нашел следующий список приоритетов полезным. Если по какой-то причине вы не можете выбрать вариант, перейдите к следующему элементу.
- Передача по значению (копируется по приемлемой цене и не требует изменения исходного объекта)
- Передать по ссылке const (не нужно менять исходный объект)
- Передача по ссылке (необходимо изменить оригинальный объект)
- Pass by shared_ptr (время жизни объекта управляется чем-то другим, это также ясно показывает намерение сохранить ссылку)
- Передача с помощью необработанного указателя (у вас есть адрес для приведения в действие, или по какой-то причине вы не можете использовать интеллектуальный указатель)
Кроме того, если ваше рассуждение выбрать следующий элемент из списка - это "производительность", то сядьте и измерьте разницу. По моему опыту, большинство людей (особенно на фоне Java или С#) склонны переоценивать стоимость передачи объекта по стоимости (и недооценивать стоимость разыменования). Передача по значению является самым безопасным вариантом (это не вызовет сюрпризов вне объекта или функции, даже в другом потоке), не отказывайтесь от этого огромного преимущества.
Ответ 4
Много времени это будет зависеть от контекста, например, если это функтор, который будет вызываться в for_each или подобном, то вы часто храните ссылку или указатель внутри вашего функтора на объект, который, как вы ожидаете, будет иметь срок службы за пределами вашего функтора.
Если это общий класс использования, вам нужно подумать о том, как люди будут его использовать.
Если вы пишете токенизатор, вам нужно учитывать, что копирование того, что вы используете, может быть дорогостоящим, однако вам также необходимо учитывать, что если вы пишете библиотеку boost, которую вы пишете для широкой публики, которая будет использовать это многоцелевым способом.
Сохранение a const char *
будет лучше, чем std::string const&
здесь. Если у пользователя есть std::string
, то const char *
останется действительным, если они не изменяют свою строку, и они, вероятно, не будут. Если у них есть const char * или что-то, что содержит массив символов и передает его, оно все равно скопирует его, чтобы создать std::string const &
, и вам грозит большая опасность того, что он не пройдет мимо вашего конструктор.
Конечно, при const char * вы не можете использовать все прекрасные функции std::basic_string
в своей реализации.
Есть опция взять в качестве параметра a std::string&
(не const-ссылку), которая должна гарантировать (с совместимым компилятором), что никто не пройдет во временное, но вы сможете документировать, Фактически это изменит, и обоснование вашего, казалось бы, неконстантичного кода. Заметьте, я тоже использовал этот трюк в своем коде. И вы можете с удовольствием использовать функции поиска строк. (Также, если вы хотите, взяв basic_string, а не строку, чтобы вы могли также обозначать широкие символьные строки).