Pergunta

Due to copy elision, it is generally preferred to pass objects by value, as long as an internal copy is retained. What about the following situation:

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
};

Will chained copies still be elided, i.e. are 1, 3 and 5 preferable? Or rather 2, 4, 6? Does it depend on inlining?

Foi útil?

Solução

In your examples I don't think it makes much difference. The constructor of A has no side-effects, so the standard's rules on copy elision are irrelevant. All that matters is what optimizers can realistically achieve. Once the constructor is inlined, I'd expect that's about the same in both cases, and there's no particular reason why this constructor can't be inlined (it's small and it's defined in the class definition).

If either the B constructor cannot be inlined or the A constructor has side-effects, then the copy to the data member must happen (same for the base class subobjects in C and D). The standard's rules on copy elision don't permit the copy from the parameter to the data member to be elided, so that is the situation where realistically you'll see a copy that you don't need.

Supposing that the type A has a move constructor that is more efficient than copying, the following is a clear win in the case where the caller passes a temporary object:

struct B
{
    B(A a) : a(std::move(a)) {}
    A a;
};

The reason is that this can construct the temporary directly into the function argument and then move it into the data member. 2/4/6 cannot (safely) move into the data member from an lvalue reference-to-const. 1/3/5 can safely move since the parameter a isn't used again, but the compiler isn't allowed to make that change for itself unless either (a) you give it permission with std::move, or (b) they're equivalent under the "as-if" rule.

In the case where the caller passes an lvalue, my constructor might be marginally slower than (2), since my constructor copies and then moves, whereas (2) just copies. You can ignore this cost (since moves are supposed to be very cheap), or you can write separate const A& and A&& constructors. I expect the general rule of thumb is to do the former.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top