Why not move the semantics and work of the RVO as expected?

I recently stumbled upon some strange behavior in my solution to an equation that made me ask myself if I really understood how semantics and RVO move together.

There are many related questions in this forum , and I also read many common explanations on this. But my problem seems pretty specific, so I hope someone helps me.

The nested structure is a bit complicated, but it breaks, at least until then:

struct Foo
{
    Bar* Elements;

    Foo(void) : Elements(nullptr)
    {
        cout << "Default-constructing Foo object " << this << endl;
    }

    Foo(Foo const& src) : Elements(nullptr)
    {
        cout << "Copying Foo object " << &src << " to new object " << this << endl;
        if (src.Elements != nullptr)
        {
            Allocate();
            copy (src.Elements, src.Elements + SIZE, Elements);
        }
    }

    Foo(Foo&& src) : Elements(nullptr)
    {
        cout << "Moving Foo object " << &src << " into " << this << endl;
        Swap(src);
    }

    ~Foo(void)
    {
        cout << "Destructing Foo object " << this << endl;
        Deallocate();
    }

    void Swap(Foo& src)
    {
        cout << "Swapping Foo objects " << this << " and " << &src << endl;
        swap(Elements, src.Elements);
    }

    void Allocate(void)
    {
        Elements = new Bar[SIZE]();
    }

    void Deallocate(void)
    {
        delete[] Elements;
    }

    Foo& operator=(Foo rhs)
    {
        cout << "Assigning another Foo object to " << this << endl;
        Swap(rhs);
        return *this;
    }

    Foo& operator+=(Foo const& rhs)
    {
        cout << "Adding Foo object " << &rhs << " to " << this << endl;
        // Somehow adding rhs to *this
        cout << "Added Foo object" << endl;
        return *this;
    }

    Foo operator+(Foo rhs) const
    {
        cout << "Summing Foo objects" << endl;
        return rhs += *this;
    }

    static Foo Example(void)
    {
        Foo result;
        cout << "Creating Foo example object " << &result << endl;
        // Somehow creating an 'interesting' example
        return result;
    }
};

Now consider the following short program:

int main()
{
    Foo a = Foo::Example();
    cout << "Foo object 'a' is stored at " << &a << endl;
    Foo b = a + a;
    cout << "Foo object 'b' is stored at " << &b << endl;
}

These were my expectations before I ran this code:

  • The method Examplecreates an instance of a local object Foo, as a result of which the default ctor is called.
  • Example Foo . , , - RVO.
  • ctor . a Example.
  • a + a, operator+ .
  • , .
  • operator+= *this .
  • operator+= , return operator+.
  • , , . , b ( 2 3).
  • a b , .

( , ) , 8 ( , ). :

01  Default-constructing Foo object 0x23fe20
02  Creating Foo example object 0x23fe20
03  Foo object 'a' is stored at 0x23fe20
04  Copying Foo object 0x23fe20 to new object 0x23fe40
05  Summing Foo objects
06  Adding Foo object 0x23fe20 to 0x23fe40
07  Added Foo object
08  Copying Foo object 0x23fe40 to new object 0x23fe30
09  Destructing Foo object 0x23fe40
10  Foo object 'b' is stored at 0x23fe30
11  Destructing Foo object 0x23fe30
12  Destructing Foo object 0x23fe20

operator+ :

Foo operator+(Foo rhs) const
{
    cout << "Summing Foo objects" << endl;
    rhs += *this;
    return rhs;
}

:

01  Default-constructing Foo object 0x23fe20
02  Creating Foo example object 0x23fe20
03  Foo object 'a' is stored at 0x23fe20
04  Copying Foo object 0x23fe20 to new object 0x23fe40
05  Summing Foo objects
06  Adding Foo object 0x23fe20 to 0x23fe40
07  Added Foo object
08  Moving Foo object 0x23fe40 into 0x23fe30
09  Swapping Foo objects 0x23fe30 and 0x23fe40
10  Destructing Foo object 0x23fe40
11  Foo object 'b' is stored at 0x23fe30
12  Destructing Foo object 0x23fe30
13  Destructing Foo object 0x23fe20

, rhs x (, , return move(rhs += *this);) move ctor.

, -fno-elide-constructors :

01  Default-constructing Foo object 0x23fd30
02  Creating Foo example object 0x23fd30
03  Moving Foo object 0x23fd30 into 0x23fe40
04  Swapping Foo objects 0x23fe40 and 0x23fd30
05  Destructing Foo object 0x23fd30
06  Moving Foo object 0x23fe40 into 0x23fe10
07  Swapping Foo objects 0x23fe10 and 0x23fe40
08  Destructing Foo object 0x23fe40
09  Foo object 'a' is stored at 0x23fe10
10  Copying Foo object 0x23fe10 to new object 0x23fe30
11  Summing Foo objects
12  Adding Foo object 0x23fe10 to 0x23fe30
13  Added Foo object
14  Moving Foo object 0x23fe30 into 0x23fe40
15  Swapping Foo objects 0x23fe40 and 0x23fe30
16  Moving Foo object 0x23fe40 into 0x23fe20
17  Swapping Foo objects 0x23fe20 and 0x23fe40
18  Destructing Foo object 0x23fe40
19  Destructing Foo object 0x23fe30
20  Foo object 'b' is stored at 0x23fe20
21  Destructing Foo object 0x23fe20
22  Destructing Foo object 0x23fe10

, ,

  • RVO ( ),
  • ( )
  • ( ),

. : - , 8 (, , , )? .

gcc mingw-w64 x86-64 v.4.9.2 -std=c++11 .

p.s. - , , OO- ; -)

+4
2

NRVO ( - NRVO?), ( ?)

- :

Foo operator+(Foo const& rhs) const
{
    cout << "Summing Foo objects" << endl;
    Foo res{*this};
    res += rhs;
    return res;
}
+3

, :

Foo operator+(const Foo& rhs) const
{
    cout << "Summing Foo objects" << endl;
    Foo result(rhs);
    result += *this;
    return result;
}

NRVO. " ", . , .

, Boost.Operators df.operators, .

+2

All Articles