3

I would like to create an object, that I'm only going to use only once, by a function call.

The two approaches I've tried give the same result, but as I'm new to C++, I'm not sure if they're appropriate.

#include <iostream>
using namespace std;

struct Foo {
    Foo(const int x, const int y): x(x), y(y) {};
    Foo(Foo & myfoo): x(myfoo.x), y(myfoo.y) {
        cout << "copying" << endl;
    };
    const int x, y;
};

int sum(const Foo & myfoo) {
    return myfoo.x + myfoo.y;
}

Foo make_foo(const int x, const int y) {
    Foo myfoo (x, y);
    return myfoo;
}

int main () {
    // version 1
    cout << 1 + sum(Foo(2,3)) << endl;
    // version 2
    cout << 1 + sum(make_foo(2,3)) << endl;
}

Which of these approaches is "more correct"? What's the difference? I ran the code above with gcc and clang and both times I get

6
6

which means the copy constructor wasn't called either time.

Related: Calling constructors in c++ without new

Edit: Thanks, I already know RVO. I was just wondering if one method is preferred over the other

Community
  • 1
  • 1
  • 1
    My guess is the compiler is optimizing `make_foo` by inlining it (since it doesn't do anything meaningful). In this case, `make_foo` doesn't even encapsulate any degree of complex meaning, and I would just use `Foo(2,3)` – Assimilater Jul 06 '16 at 16:41
  • 1
    side comment: your copy constructor, for best practice, should pass `const Foo&` – Assimilater Jul 06 '16 at 16:42
  • 3
    RVO (https://en.wikipedia.org/wiki/Return_value_optimization) – Sebastian Hoffmann Jul 06 '16 at 16:42

4 Answers4

2

The two approaches are equivalent in this case.

What you are observing is Copy elision, or more specifically Return Value Optimization.

The compiler is smart enough to recognize that you don't need to construct the intermediate instance since you are just going to copy, then destroy it. Therefore, it just creates the final instance which is the target of the function return.

This optimization is explicitly allowed by the language standard, even though it means that your copy constructor gets bypassed. In other words, the functionality of a copy constructor getting called is not guaranteed.

Brent Bradburn
  • 40,766
  • 12
  • 126
  • 136
  • 1
    It's not guaranteed... in this case. If you were to do `Foo b(make_foo(1, 2))`, you're guaranteed that it'll be called to construct `b`. Minor nitpick, but C++ is all about those. – Fund Monica's Lawsuit Jul 06 '16 at 16:54
0

The copy constructor isn't called because in sum(const Foo & myfoo), myfoo is being passed by reference and so doesn't need to be copied. IMHO I would say that a generally applicable constructor for Foo suffices for creating an instance. If you have specialized functionality that would clutter your Foo class with constructors, it might be more elegant to have a function elsewhere, like Foo loadFooFromFile(string filename)

alter igel
  • 5,696
  • 3
  • 16
  • 38
  • 1
    The OP isn't concerned about the copy operation at the call of `sum`, but instead from the return value of `make_foo` – Assimilater Jul 06 '16 at 16:46
0

It is worth noting that functions that are usually named with make_* are useful for types that require template parameters (and they could be deduced during construction).

In C++ class template parameters are not deduced when calling the constructor (this will change in C++17) so you have to type them manually. By making a function that creates an object you can have it automatically deduced for you.

For example look up std::tuple, or std::pair

edit

I found a very related thread Deduction of the function

Community
  • 1
  • 1
Sopel
  • 1,073
  • 1
  • 9
  • 14
  • For future reference, I would not use the "edit" headers in answers. SO is meant to be edited, no need to announce every edit. Just make it look like a better answer and we can see the edits if we need to :) – Assimilater Jul 06 '16 at 19:26
0

In the first case cout << 1 + sum(Foo(2,3)) << endl; copy constructor will not be called since sum accepts the Foo reference. For later case your compiler is doing RVO hence copy constructor is not invoked. For more details read through Return Value Optimization and Copy Elision. Also this question has more details.

Community
  • 1
  • 1