#c #move #template-argument-deduction
#c #двигаться #шаблон-аргумент-вывод
Вопрос:
Ниже приведен упрощенный пример шаблона List
, где есть две append_move()
append_forward()
функции и, которые имеют одну и ту же цель, берут аргументы и помещают их в контейнер List
. Первая append_move()
функция принимает arg1
переданное значение, а затем перемещает его в emplace_back()
функцию. Вторая append_move()
функция использует автоматическое arg1
восстановление, а затем перенаправляет его в emplace_back()
функцию.
Имеет ли append_forward()
функция какие-либо преимущества перед append_move()
функцией и какой функции следует отдать предпочтение?
#include lt;dequegt; #include lt;stringgt; #include lt;utilitygt; templatelt;typename Tgt; class List { public: void append_move(T arg1, std::string arg2) { list.emplace_back(std::move(arg1), std::move(arg2)); } templatelt;typename Xgt; void append_forward(Xamp;amp; arg1, std::string arg2) { list.emplace_back(std::forwardlt;Xgt;(arg1), std::move(arg2)); } private: std::dequelt;std::pairlt;T, std::stringgt;gt; list; };
Комментарии:
1. Подумайте, что происходит, когда вы создаете что-то вроде
Listlt;int amp;gt;
. Вы хотите разрешить или запретить это?2. добавить перемещение скопирует arg1 при вызове, append_forward не будет
3. @ChrisDodd Не будет работать, коллекции ссылок также не допускаются. (если вы не хотите специализироваться на шаблонах и начать использовать std::reference_wrapper)
4. @PepijnKramer: это зависит от контейнера-они могут быть реализованы для прямой поддержки ссылок. Вот почему я спросил, хотите ли вы разрешить или запретить это.
5. @Kam, если
T
у вас есть какие-либо явные однопараметрические конструкторы, то эти два понятия даже не будут эквивалентны с семантической точки зрения.
Ответ №1:
Вперед или вперед
Если T
деструктор не может быть оптимизирован и создает видимые побочные эффекты, например
struct Loud { ~Loud() { std::cout lt;lt; "destructorn"; } };
затем
Listlt;Loudgt; list; Loud const loud; list.append_forward(loud);
вызывает на один деструктор меньше, чем
list.append_move(loud);
поскольку последний создает еще один объект =gt; должен вызвать еще один деструктор. Таким образом, пересылка более предпочтительна.
Однако это делает API менее привлекательным:
another_list.append_move({"some", "arguments"}); // works and pretty //another_list.append_forward({"some", "arguments"}); // doesn't compile another_list.append_forward(Foo{"some", "arguments"}); // works
Перегрузка вручную
Так что, к сожалению, обеспечение рукописных перегрузок, по-видимому, является лучшим решением (для пользователя вашего кода) из этих трех:
void append_overload(Tamp;amp; t); // TODO actually implement void append_overload(T constamp; t) { append_overload(T{t}); }
Место
Однако (еще раз), если вас все это волнует, подумайте о том, чтобы разместить:
templatelt;typename... Asgt; void append_emplace(Asamp;amp;... args) { list.emplace_back(std::forwardlt;Asgt;(args)...); }
// works and pretty as long as you don't need to pass-construct two arguments another_list.append_emplace("some", "arguments");
Другие перегрузки
Кроме того, если у вас больше append_*
перегрузок, обратите внимание, что версии пересылки/размещения имеют меньший приоритет из-за того, что они являются шаблонами. Выбор за вами.
Комментарии:
1. Спасибо вам за исчерпывающий ответ. У меня есть два вопроса. 1) Когда вы говорили об одном деструкторе меньше , вы имеете в виду деструктор копии переданного объекта в параметре
arg1
append_move()
функции? 2) Меня немного смущает, что вы используетеanother_list.append_move()
another_list.append_move()
функции и с одним аргументом, потому что в моем примере я использую функции с двумя аргументами. Это просто для более легкого объяснения?2. @Kam 1) Да; Кстати, в этом случае
append_forward
constamp;
вместо этого получает, избегая создания (=gt; уничтожения) ненужного объекта. 2) Да; Кстати, реализация/использованиеappend_emplace
может быть неприятным в вашем случае (с двумя параметрами).