hansmaad
hansmaad

Reputation: 18905

How to return a member of a local object

Consider the following code:

struct Foo
{
    Foo() { cout << "Foo()\n"; }
    ~Foo() { cout << "~Foo()\n"; }
    Foo(Foo&) { cout << "Foo(Foo&)\n"; }
    Foo(Foo&&) { cout << "Foo(Foo&&)\n"; }

    int d;
};

struct Bar
{
    Foo bigData;
    void workOnBigData() { /*...*/ }
}

Foo getBigData()
{
    Bar b;
    b.workOnBigData();
    return b.bigData;
}

What is the best way to implement getBigData() in terms of copy ellision / move semantics? In this implementation the compiler seems not to be allowed to move bigData. I tested the following functions:

Foo f()
{
    Foo foo;
    return foo;  // RVO
}

Foo g()
{
    Bar b;
    return b.bigData;  // Copy
}

Foo h()
{
    Bar b;
    auto r = move(b.bigData);
    return r;  // Move
}

Can you explain the results from these implementations and show the most effective way to return a member of a local object.

Upvotes: 4

Views: 374

Answers (2)

Nikos Athanasiou
Nikos Athanasiou

Reputation: 31519

There are lots of ways to avoid extra copies, the one closer to your code would be imho:

Foo getBigData()
{
    Foo ret; // do a cheap initialization
    Bar b;
    b.workOnBigData();

    std::swap(ret, b.bigData); // 'steal' the member here

    return ret; // NRVO can apply
}

The same can achieved by move constructing the return object

Foo getBigData()
{
    Bar b;
    b.workOnBigData();     
    Foo ret(std::move(b.bigData)); // these two lines are equivalent to
    return ret;                    // return std::move(b.bigData); 
}

Upvotes: 4

user184968
user184968

Reputation:

I think answers to this question Why isn't the copy constructor elided here? are really useful in answering your question. Copy elision is not used in your example

Foo getBigData()
{
    Bar b;
    b.workOnBigData();
    return b.bigData;
}

since this requiment is not fullfiled (http://en.cppreference.com/w/cpp/language/copy_elision):

the return statement's expression is the name of a non-volatile object with automatic storage duration ... and which has the same type (ignoring top-level cv-qualification) as the return type of the function, then copy/move is omitted

In your example Bar is a variable with automatic storage duration but your return Foo. If you change your Bar class a compiler will start using copy elision:

#include <iostream>
#include <typeinfo>

using namespace std;

struct Foo
{
  Foo() { cout << "Foo()\n"; }
  ~Foo() { cout << "~Foo()\n"; }
  Foo(const Foo&) { cout << "Foo(Foo&)\n"; }
  Foo(Foo&&) { cout << "Foo(Foo&&)\n"; }

  int d;
};

struct Bar
{
  Foo bigData;
  void workOnBigData() { /*...*/ }
};

struct Bar2
{
  void workOnBigData(Foo&) { /*...*/ }
};

Foo getBigData()
{
    Bar b;
    b.workOnBigData();
    return b.bigData;
}

Foo getBigData2()
{
    Foo f;
    Bar2 b;
    b.workOnBigData(f);
    return f;
}

int main()
{
  {
    Foo f = getBigData();
  }
 cout << "---" << endl;

  {
    Foo f = getBigData2();
  }
}

#include <iostream>
#include <typeinfo>

using namespace std;

struct Foo
{
  Foo() { cout << "Foo()\n"; }
  ~Foo() { cout << "~Foo()\n"; }
  Foo(const Foo&) { cout << "Foo(Foo&)\n"; }
  Foo(Foo&&) { cout << "Foo(Foo&&)\n"; }

  int d;
};

struct Bar
{
  Foo bigData;
  void workOnBigData() { /*...*/ }
};

struct Bar2
{
  void workOnBigData(Foo&) { /*...*/ }
};

Foo getBigData()
{
    Bar b;
    b.workOnBigData();
    return b.bigData;
}

Foo getBigData2()
{
    Foo f;
    Bar2 b;
    b.workOnBigData(f);
    return f;
}

int main()
{
  {
    Foo f = getBigData();
  }
 cout << "---" << endl;

  {
    Foo f = getBigData2();
  }
}

This is what it outputs:

$ ./a.out     
Foo()
Foo(Foo&)
~Foo()
~Foo()
---
Foo()
~Foo()

Upvotes: 1

Related Questions