1

Why is the output foo3 equal to 3 ? i would suggest, when bar(foo1) is called, the function bar creates a copy of foo1 on the stack, so it's value is equal to 0, when this value is returned, the copy-constructor for foo3 increments the value again so it should be 2?

Thanks in advance.

this is my code:

#include <iostream>
struct Foo {
    Foo()
        : x(0)
    {
    }
    Foo(const Foo& foo)
        : x(foo.x + 1)
    {
    }
    int x;
};

Foo bar(Foo foo)
{
    foo.x++;
    return foo;
}

int main()
{
    Foo foo1;
    Foo foo2 = foo1;
    std::cout << "A:" << foo1.x << std::endl;
    std::cout << "B:" << foo2.x << std::endl;
    Foo foo3 = bar(foo1);
    std::cout << "C:" << foo3.x << std::endl;
}

output:

A:0
B:1
C:3
RishbhSharma
  • 259
  • 3
  • 10
denelias
  • 21
  • 1
  • 7
  • Read about [RVO and NRVO](https://en.wikipedia.org/wiki/Return_value_optimization). – Alok Save Jun 14 '16 at 15:13
  • 1
    Perhaps using a debugger and putting a break point in the copy constructor would help. – Ed Heal Jun 14 '16 at 15:16
  • A `bar` definition like this `Foo bar(Foo& foo)`, would have produced the output `C:2` – Michele Jun 14 '16 at 15:24
  • @AlokSave NRVO / RVO does not apply when the variable is the function argument. If bar was: `Foo bar(Foo const& foo){ Foo foo2(foo); foo2.x ++ ; return foo2; }` then the compiler would be allowed to do copy elision, In that case a modern compiler would probably give: `A:0 B:1 C:2` – PeterSW Jun 14 '16 at 15:45
  • Please mark one of these answers as accepted if they have answered your question. – maxlazar Jun 15 '16 at 18:24

4 Answers4

1

I believe there are three copy constructors at work here, the line foo2 = foo1, the passing of foo1 into bar, and the returning of foo1 from bar.

Modifying your code makes it clear what is happening:

#include <iostream>
struct Foo {
    Foo()
        : x(0)
    {
        std::cout << "Constructor called" << std::endl;
    }
    Foo(const Foo& foo)
        : x(foo.x + 1)
    {
        std::cout << "Copy constructor called" << std::endl;
    }
    int x;
};

Foo bar(Foo foo)
{
    std::cout << "B2:" << foo.x << std::endl;
    foo.x++;
    return foo;
}

int main()
{
    Foo foo1;
    Foo foo2 = foo1;
    std::cout << "A:" << foo1.x << std::endl;
    std::cout << "B:" << foo2.x << std::endl;
    Foo foo3 = bar(foo1);
    std::cout << "C:" << foo3.x << std::endl;
}

Output:

Constructor called
Copy constructor called
A:0
B:1
Copy constructor called
B2:1
Copy constructor called
C:3
maxlazar
  • 170
  • 7
0

When you create the first foo1, its x is zero. The call to bar passes by value, so it is copied - so the param in bar has a value of 1. The function bar itself increments x further by 1, so it is now 2. Finally, the return statement once again returns by value, so there is another copy - thus the final x is 3.

Note RVO does not apply here, because Foo is not returning a local variable by value - rather it is returning a parameter, which must actually be copied.

From the standard [class.copy/31], circumstances under which copy elision is permitted, which specifically exclude cases where function parameters are returned:

— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

Smeeheey
  • 9,906
  • 23
  • 39
  • What's the difference between a (by-value) parameter and a local variable with regards to RVO? – sepp2k Jun 14 '16 at 15:34
  • 2
    @sepp2k: The calling convention dictates where parameters and return values must be placed, there's no flexibility to make those overlap. It doesn't dictate where a temporary (RVO) or local variable (NRVO) is placed, giving the compiler the flexibility to place it directly in the return value location. – Ben Voigt Jun 14 '16 at 15:37
  • @BenVoigt That makes sense. Thanks. – sepp2k Jun 14 '16 at 15:38
  • @sepp2k - as per above edit standard specifically forbids copy elision when returning a function parameter. – Smeeheey Jun 14 '16 at 15:39
0
  • 0 -> 1 You copy foo1 to the parameter of bar.
  • 1 -> 2 You increment x within bar.
  • 2 -> 3 You copy bar to the temporary return value.

I count three increments.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

When doing:

Foo foo2 = foo1;

you are calling the copy ctor, so foo2.x's value will be 1. Then when calling:

Foo foo3 = bar(foo1);

because you are not passing by reference the argument of 'bar' is created a copy of foo1 into bar using the copy ctor (x == 1). After that that copy's x member is incremented(x == 2) and at last is returned. When returning is not created an other copy because 'return value optimization'(still x == 2). Finally foo3 is initialized using again the copy ctor(x == 3).

Andres Tiraboschi
  • 543
  • 1
  • 7
  • 17