Передать по значению или ссылке конструктору C++, которому необходимо сохранить копию?
-
13-09-2019 - |
Вопрос
Должен ли конструктор значений C++ (неявный или явный) принимать свои параметры по значению или по ссылке на константу, когда ему в любом случае необходимо сохранить копию аргумента(ов) в своем объекте?
Вот самый короткий пример, который я могу придумать:
struct foo {
bar _b;
foo(bar [const&] b) // pass by value or reference-to-const?
: _b(b) { }
};
Идея здесь в том, что я хочу свести к минимуму вызовы конструктора копирования bar при создании объекта foo любым из различных способов создания объекта foo.
Обратите внимание, что я немного знаю об исключении копирования и (именованной) оптимизации возвращаемого значения, и я прочитал «Хотите скорости?Передавать по значению", однако я не думаю, что статья напрямую касается этого варианта использования.
Редактировать: Мне следует быть более конкретным.
Предположим, что я не могу знать sizeof(bar)
, или так или иначе bar
является фундаментальным встроенным типом (bar
может быть параметром шаблона, и foo
может быть шаблоном класса вместо класса).Кроме того, не думайте, что foo
конструктор может быть встроенным (или bar
да, если уж на то пошло).Предположим, что я, по крайней мере, использую компилятор, реализующий RVO.
Мне бы хотелось, чтобы была возможность (с учетом оптимизации компилятора), что такой вызов не будет вызывать никаких вызовов bar
конструктор копирования (даже при выполнении _b(b)
в foo
список инициализации):
foo f = function_that_creates_and_returns_a_bar_object_using_rvo();
Есть ли какая-либо возможность (учитывая стандарт C++98), что это можно сделать, и если да, то с большей или меньшей вероятностью это сработает, если foo
принимает свой параметр по ссылке на константу, а не по значению?
Решение
В C++98 и C++03 вам следует передать const& bar
а затем скопируйте.В C++0x вы должны передать bar
а затем сделать ход (при условии bar
имеет конструктор перемещения).
#include <utility>
struct foo
{
bar _b;
foo(bar b) : _b(std::move(b)) {}
};
Если вы создадите foo с параметром lvalue, будет вызван конструктор копирования для создания копии. b
, и эта копия будет перемещена в _b
.Если вы создадите foo с параметром rvalue, bar
будет вызван конструктор перемещения для перехода в b
, а затем он снова будет перемещен в _b
.
Другие советы
При прочих равных условиях я пропускаю const& для достаточно сложных классов и значение для POD и простых объектов.
Изложение плюсов и минусов передачи по константной ссылке вместо традиционной передачи по значению
Положительные стороны:
- Избегает копирования (большой плюс для объектов с дорогой копией)
- Доступ только для чтения
Негативы:
- Кто-то может использовать const для ссылки, если он действительно этого хочет.
Что еще важнее из положительных моментов, так это вы. явно контролировать, когда происходит копирование (в вашем случае при передаче инициализации _b в вашем списке инициализаторов).Думая о минусах...Я согласен, что это риск.Я думаю, что почти все хорошие программисты будут чувствовать себя неловко из-за использования const_cast.Более того, вы можете послушно найти const_cast и закидать помидорами человека, выбрасывающего константу из аргумента.Но эй, мало ли, а у кого есть время смотреть код, как ястреб :)?
Мое субъективное мнение таково, что в достаточно сложных классах и в средах, где производительность имеет значение, польза от отказа от конструктора копирования перевешивает риск.Однако для действительно тупых классов и классов POD я склонен делать копии данных и передавать их по значению.
Посмотри это вопрос.
Использовать const T & arg
если sizeof(T)>sizeof(void*)
и использовать T arg
если sizeof(T) <= sizeof(void*)
.Все фундаментальные типы должны быть исключением из этого правила.
Стилистически я бы сказал, что передача по ссылке — лучший способ.
Если производительность действительно имеет значение, то не угадывайте.Измерьте это.
Я не проверял, что говорит стандарт, но, попробовав это эмпирически, могу сказать вам, что GCC не оптимизирует копию, независимо от того, принимает ли конструктор аргумент по значению или по константной ссылке.
Однако если конструктор принимает константную ссылку, ему удается избежать ненужной копии при создании фу из существующего бар объект.
Обобщить:
Bar b = makeBar(); // No copy, because of RVO
FooByValue f1 = makeBar(); // Copy constructor of Bar called once
FooByRef f2 = makeBar(); // Copy constructor of Bar called once
FooByValue f3(b); // Copy constructor of Bar called twice
FooByRef f4(b); // Copy constructor of Bar called only once
Не то чтобы я эксперт по компилятору, но я думаю, имеет смысл то, что обычно он не может RVO возвращаемого значения в произвольном месте (например, в поле-члене фу объект).Вместо этого требуется, чтобы цель находилась на вершине стека.
Я предполагаю, что bar
имеет обычный конструктор копирования формы bar(const bar &b)
Возьмите ссылку на константу здесь. bar
Конструктор затем возьмет эту ссылку и выполнит копирование.Всего копий:1.
Если вы отключите ссылку, компилятор создаст копию b и передаст копию foo
конструктор, который затем передаст его bar
и скопируйте это.
Честно говоря, для этого случая лучший вариант — сделать свой конструктор inline
, предоставил bar
конструктор не выдает.Тогда просто передайте по ссылке.
Кроме того, как вы и подозревали, ваша статья здесь не применима.
Удерживайте общий_указатель на панель в своем классе и передайте его как таковой.Таким образом, вы никогда не вызываете конструктор копирования :).