Из-за исключения копирования, как правило, предпочтительнее передать объекты по значению, пока сохраняется внутренняя копия. Как насчет следующей ситуации:
struct A
{
A(int x, int y) : x(x), y(y) {}
int x, y;
};
struct B
{
B(A a) : a(a) {} // 1
B(const A& a) : a(a) {} // 2
A a;
};
struct C : A
{
C(A a) : A(a) {} // 3
C(const A& a) : A(a) {} // 4
};
struct D : B, C
{
D(A a) : B(a), C(a) {} // 5
D(const A& a) : B(a), C(a) {} // 6
};
Будут ли удалены цепочечные копии, то есть предпочтительнее 1, 3 и 5? А точнее 2, 4, 6? Это зависит от встраивания?
В ваших примерах я не думаю, что это имеет большое значение. Конструктор A
не имеет побочных эффектов, поэтому правила стандарта об исключении копирования не имеют значения. Все, что имеет значение, — это то, чего реально могут достичь оптимизаторы. Как только конструктор будет встроен, я ожидаю, что в обоих случаях он будет примерно одинаковым, и нет особой причины, по которой этот конструктор не может быть встроен (он маленький и определен в определении класса).
Если либо B
конструктор не может быть встроен или A
конструктор имеет побочные эффекты, тогда должно произойти копирование в элемент данных (то же самое для подобъектов базового класса в C
а также D
). Стандартные правила об исключении копирования не допускают исключения копии из параметра в элемент данных, поэтому в действительности вы увидите копию, которая вам не нужна.
Предположим, что тип A
имеет конструктор перемещения, который более эффективен, чем копирование, следующее является очевидным преимуществом в случае, когда вызывающая сторона передает временный объект:
struct B
{
B(A a) : a(std::move(a)) {}
A a;
};
Причина в том, что это может создать временный объект непосредственно в аргумент функции, а затем переместить его в элемент данных. 2/4/6 не может (безопасно) перейти в элемент данных из lvalue reference-to-const. 1/3/5 может безопасно двигаться, так как параметр a
больше не используется, но компилятору не разрешено вносить это изменение самостоятельно, если только (а) вы не дадите ему разрешение с std::move
или (b) они эквивалентны по правилу «как будто».
В случае, когда вызывающая сторона передает lvalue, мой конструктор может быть немного медленнее, чем (2), так как мой конструктор копирует, а затем перемещается, тогда как (2) просто копирует. Вы можете игнорировать эту стоимость (так как ходы должны быть очень дешевыми), или вы можете написать отдельно const A&
а также A&&
Конструкторы. Я ожидаю, что общее правило должно делать первое.
Других решений пока нет …