0

I am trying to get an rvalue instance of this class:

#include <iostream>
#define msg(x) std::cout << x " constructor\n"

struct X {
    int i;
    X(int i) : i(i) {msg("X");}
    X(const X& x) : i(x.i) {std::cout << "X copy\n";}
    X(X&& x) {std::swap(i, x.i); std::cout << "X move\n";}
};

into instance variable x of this class:

struct A {
    X x;
    A(X x) : x(x) {msg("A");}
};

like so:

int main() {
    A a(X(1));
    std::cout << a.x.i << "\n\n";
}

without any copies or moves being made.

According to these references,

and many many posts on SO (so please read to the end before flagging as duplicate), I should rely on copy elision, whose conditions should be satisfied if I pass by value. Note that there are two copy elisions required, namely:

constructor call -> constructor local variable -> instance variable

as can be seen when turning copy elision off (compile with g++-4.8 -std=c++11 -fno-elide-constructors):

X constructor
X move
X copy
A constructor
1

So there is one move step and one copy step, which should both go away if I turn copy elision on (compile with g++-4.8 -std=c++11 -O3):

X constructor
X copy
A constructor
1

Bummer, the copy step remained!


Can I get any better with any other variation of std::move(), std::forward or passing as rvalue-reference?

struct B {
    X x;
    B(X x) : x(std::move(x)) {msg("B");}
};

struct C {
    X x;
    C(X x) : x(std::forward<X>(x)) {msg("C");}
};

struct D {
    X x;
    D(X&& x) : x(std::move(x)) {msg("D");}
};

int main() {
    B b(X(2));
    std::cout << b.x.i << "\n\n";

    C c(X(3));
    std::cout << c.x.i << "\n\n";

    D d(X(4));
    std::cout << d.x.i << "\n\n";
}

which produces the output:

X constructor
X move
B constructor
2

X constructor
X move
C constructor
3

X constructor
X move
D constructor
4

OK, I turned the copy into a move, but this is not satisfactory!


Next, I tried to make the instance variable x a reference X&:

struct E {
    X& x;
    E(X x) : x(x) {msg("E");}
};

int main() {
    E e(X(5));
    std::cout << e.x.i << "\n\n";
}

which produces:

X constructor
E constructor
1690870696

Bad idea! I got rid of the move but the rvalue instance that x was referencing to got destroyed under my seat, so the last line prints garbage instead of 5. Two notes:

  • g++-4.8 didn't warn me of anything, even with -pedantic -Wall -Wextra
  • The program prints 5 when compiled with -O0

So this bug may go unnoticed for quite a while!


So, is this a hopeless case? Well no:

struct F {
    X& x;
    F(X& x) : x(x) {msg("F");}
};

int main() {
    X x(6);
    F f(x);
    std::cout << f.x.i << "\n";
}

prints:

X constructor
F constructor
6

Really? No fancy new C++11 features, no copy elision at the discretion of the compiler, just plain old FORTRAN66-style pass-by-reference does what I want and probably will perform best?

So here are my questions:

  • Is there any way one can get this to work for rvalues? Did I miss any features?
  • Is the lvalue-reference version really the best, or are there hidden costs in the X x(6) step?
  • Could there be any inconveniences introduced by x living on after the construction of f?
  • Could I pay a data locality penalty for using the lvalue reference to an external instance?
Stefan
  • 4,380
  • 2
  • 30
  • 33
  • `struct E` has undefined behaviour (dangling reference.) And `struct F` "works", but does something totally different to all the rest (bar the UB `E`). So what are you trying to compare? – juanchopanza May 29 '14 at 22:18
  • @juanchopanza: Yes, I learned the behavior of `struct E` the hard way... – Stefan May 29 '14 at 22:20
  • Your best bet is two overloads: `T1(const T2& t) : t_(t) {}` and `T1(T2&& t) : t_(std::move(t)) {}` – juanchopanza May 29 '14 at 22:22
  • @juanchopanza: All versions, including `struct F`, can fully access instance `x`, which is what I need. – Stefan May 29 '14 at 22:25
  • No, `F` has a reference to an external entity. It doesn't own it. The others (ignoring `E`) do. This is a crucial difference. – juanchopanza May 29 '14 at 22:26

1 Answers1

2

Without going into too much detail of your question, copy elision is basically used as much as possible. Here's a quick demo:

#include <iostream>
#include <utility>

struct X
{
    int n_;

    explicit X(int n) : n_(n) { std::cout << "Construct: " << n_ << "\n"; }
    X(X const & rhs) : n_(rhs.n_) { std::cout << "X copy:" << n_ << "\n"; }
    X(X && rhs) : n_(rhs.n_) { rhs.n_ = -1; std::cout << "X move:" << n_ << "\n"; }
   ~X() { std::cout << "Destroy: " << n_ << "\n"; }
};

struct A
{
    explicit A(X x) : x_(std::move(x)) {};
    X x_;
};

struct B
{
    X x;
};

int main()
{
    A a(X(12));
    B b { X(24) };
}

This produces:

Construct: 12
X move:12
Destroy: -1
Construct: 24
Destroy: 24
Destroy: 12

The one move in x_(std::move(x)) is not elidable, since it doesn't involve a function return. But that's pretty good anyway. And notice how the aggregate b is indeed initialized "in-place".


Your example F shows that you're willing to expose the coupling of X to its ambient class. In that case, you could make a special constructor for A that constructs the X directly:

struct A
{
    explicit A(X x) : x_(std::move(x)) {};
    X x_;

    // Better approach
    struct direct_x_t {};
    static direct_x_t direct_x;

    // In our case, this suffices:
    A(direct_x_t, int n) : x_(n) {}

    // Generally, a template may be better: (TODO: add SFINAE control)
    template <typename ...Args> A(direct_x_t, Args &&... args)
    : x_(std::forward<Args>(args)...) {}
};

Usage:

A a(A::direct_x, 36);
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Thanks! So do you think `struct B` is the best? What's wrong with `struct F`, which is my favorite? :) – Stefan May 29 '14 at 22:30
  • 1
    @Stefan: Most times you won't be able to have aggregate classes, so that may not be an option. Personally, I'm perfectly happy with `struct A` and would use that as a general model. `F` is insane; you should essentially never have reference members unless you *really* know what you're doing and are ready to support your users at 4am. – Kerrek SB May 29 '14 at 22:32
  • @Stefan: If you're willing to expose the coupling to `X`, you can try the alternate approach I added. It's similar to `emplace` in various standard library containers. – Kerrek SB May 29 '14 at 22:36
  • In my use case, I am free to use `struct F` if I want to. My evaluation suggests that this will save me one move operation. Both `x` and `f` will live in the same tiny scope. – Stefan May 29 '14 at 22:42
  • @KerrekSB why does the struct A with forwarding mechanic allow the compiler to construct the object in-place while a constructor moving a temporary does not? Does the variadic template play a role in allowing it? – qeadz May 29 '14 at 22:49
  • @KerrekSB - nevermind. I see now. Silly me misreading that as passing in the X(6) when its not. Its just passing through the actual construction argument that X needs (as in your Usage which I didnt pay attention to). – qeadz May 29 '14 at 22:52
  • @KerrekSB: I'm looking into the template suggestion. My actual `rvalue` comes from a method call of another class, so I'm not sure if I can use this in this case. – Stefan May 29 '14 at 22:54
  • @Stefan: I'm mostly certain that there'll be absolutely no measurable performance difference. Why are you trying so hard to avoid one move? And if you have a class that cannot be moved at all (e.g. a mutex), then copy elision doesn't apply anyway. – Kerrek SB May 29 '14 at 23:01
  • I ended up refactoring my code so that `X` is constructed inside of `A`. Thanks for your help! – Stefan May 30 '14 at 13:06