Копировать elision в цепочечном вызове конструкторов

Из-за исключения копирования, как правило, предпочтительнее передать объекты по значению, пока сохраняется внутренняя копия. Как насчет следующей ситуации:

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? Это зависит от встраивания?

1

Решение

В ваших примерах я не думаю, что это имеет большое значение. Конструктор 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&& Конструкторы. Я ожидаю, что общее правило должно делать первое.

2

Другие решения

Других решений пока нет …