Rvalue references

Hello everybody.
I wrote this code, just to see if I can correctly implement the move constructor and the rvalue assignment operator.

It seems that I couldn't.
Building this on VS2010 "Release" mode, it enters a ~Little() destruction frenzy at runtime.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <stdexcept>

class Little {
public:

    int i;

    Little(): i(0) {
        std::clog << "Little()" << std::endl;
    }

    explicit Little(int i): i(i) {
        std::clog << "Little(" << i << ')' << std::endl;
    }

    Little(const Little &l): i(l.i) {
        std::clog << "Little(const Little &)" << std::endl;

        if (this == &l)
            throw std::invalid_argument("self assignment");
    }

    Little & operator = (const Little &l) {
        std::clog << "Little & operator = (const Little &)" << std::endl;

        if (this == &l)
            throw std::invalid_argument("self assignment");

        i = l.i;
        return *this;
    }

    ~Little() {
        std::clog << "~Little()" << std::endl;
    }
};

class Big {
private:

    Little *pl;
    std::size_t s;

public:

    explicit Big(std::size_t s): s(s) {
        std::clog << "Big(" << s << ')' << std::endl;
        pl = new Little[s];
        std::fill(pl, pl + s, Little());
    }

    Big(std::size_t s, int i): s(s) {
        std::clog << "Big(" << s << ", " << i << ')' << std::endl;
        pl = new Little[s];
        std::fill(pl, pl + s, Little(i));
    }

    Big(const Big &b): s(b.s) {
        std::clog << "Big(const Big &)" << std::endl;

        if (this == &b)
            throw std::invalid_argument("self assignment");

        if (b.pl == nullptr)
            throw std::invalid_argument("nullptr pl");

        pl = new Little[s];
        std::copy(b.pl, b.pl + b.s, pl);
    }

    Big(Big &&b): s(b.s), pl(b.pl) {
        std::clog << "Big(Big &&)" << std::endl;

        if (b.pl == nullptr)
            throw std::invalid_argument("nullptr pl");

        b.s = 0;
        b.pl = nullptr;
    }

    Big & operator = (const Big &b) {
        std::clog << "Big & operator = (const Big &)" << std::endl;

        if (this == &b)
            throw std::invalid_argument("self assignment");

        if (b.pl == nullptr)
            throw std::invalid_argument("nullptr pl");

        s = b.s;
        delete[] pl;
        pl = new Little[s];
        std::copy(b.pl, b.pl + b.s, pl);
        return *this;
    }

    Big & operator = (Big &&b) {
        std::clog << "Big & operator = (Big &&)" << std::endl;

        if (b.pl == nullptr)
            throw std::invalid_argument("nullptr pl");

        std::swap(s, b.s);
        std::swap(pl, b.pl);
        return *this;
    }

    void info() {
        std::clog << "void info()" << std::endl;
        std::cout << "---------------------------------------\n";
        std::cout << "Big @ " << this << " information\n";
        std::cout << "---------------------------------------\n";
        std::cout << "s:\t\t" << s << '\n';
        std::cout << "(*pl).i:\t" << (*pl).i << '\n';
        std::cout << "pl:\t\t" << pl << std::endl;
        std::cout << "---------------------------------------\n";
    }

    void setpl(int i) {
        std::clog << "void setpl(" << i << ')' << std::endl;
        std::fill(pl, pl + s, Little(i));
    }

    ~Big() {
        std::clog << "~Big()" << std::endl;
        delete[] pl;
    }
};

Big && createBig() {
    return Big(2, 13);
}

int main() {
    Big b(createBig());
    b.info();
    return 0;
}


Edit: todo list
- fill() should be changed to fill_n()
Last edited on
The usual rules for returning references to local variables apply, even if it's an rvalue reference.
So it should be just Big createBig().
Last edited on
you must be getting error when you compile the code .

I dont understand the purpose to this constuctor nor do i undertand the constructor.

1
2
3
4
5
6
7
8
9
Big(Big &&b): s(b.s), pl(b.pl) {
        std::clog << "Big(Big &&)" << std::endl;

        if (b.pl == nullptr)
            throw std::invalid_argument("nullptr pl");

        b.s = 0;
        b.pl = nullptr;
    }



This line in the constructor is wrong ..

pl = new Little[s];

you cannot allocate array this way , you can allocate single object , like

pl = new Little();

I dont understand the purpose to this constuctor nor do i undertand the constructor.

It's a move constructor.

pl = new Little[s];
you cannot allocate array this way

Sure you can.
Thanks Athar ,,

but this is new to me .. if you can please elobrate on

pl = new Little[s];

I can't figure it out ..
Aftter fixing the error pointed out by Athar (and by gcc and by clang), the output of your program is

Big(2, 13)
Little()
Little()
Little(13)
Little & operator = (const Little &)
Little & operator = (const Little &)
~Little()
void info()
---------------------------------------
Big @ 0x7fff7df9cf68 information
---------------------------------------
s:              2
(*pl).i:        13
pl:             0x604018
---------------------------------------
~Big()
~Little()
~Little()


What output did you expect?

createBig(2,13) calls the two-argument ctor, which constructs main's b (no copy is made, due to RVO)

that ctor creates an array of two Littles, so it makes two calls to Little's default ctor

then the argument to fill() is evaluated, it's Little(13), which is the next line of output

That argument is copy-assigned into the two positions of the array (that's what fill does)

then the temporary used by fill() is destructed

and then you're calling info()

Thanks for the replies...

What output did you expect?

The problem was that the ~Little() destructor was calling infinitely.

createBig(2,13) calls the two-argument ctor, which constructs main's b (no copy is made, due to Return Value Optimization)

Huh, didn't expect that. So how exactly can I test the move constructor?
I'm pretty sure this will force it...
 
Big b(std::move(createBig()));
Indeed it does. For anyone interested, that move() is in utility, not algorithm.
Also here's the updated version, not posted here to save space: http://pastebin.com/qRTJB7ge

Slightly outside the scope of this thread, please do let me know if you notice any bug. Thanks.
Ideally, move constructors, move assignment operators, and destructors should not throw and should have a noexcept specification if at all possible.

For example:
1
2
3
4
5
6
Big( Big&& b ) noexcept : s(b.s), pl(b.pl)
{
        b.s = 0 ;
        b.pl =  nullptr ;
}
// etc ... 


Or in the general case,
1
2
3
4
5
6
7
8
9
10
11
12
template< typename T > struct A
{
    explicit A( const T& t ) : data(t) {}

    A( const A& that ) : data(that.data) {}

    A( A&& that ) noexcept( std::is_nothrow_move_constructible<T>::value )
             : data( std::move(that.data) ) {}
    // etc

    T data ;
};


http://thbecker.net/articles/rvalue_references/section_09.html

JLBorges wrote:
Ideally, move constructors, move assignment operators, and destructors should not throw and should have a noexcept specification if at all possible.


Right, if Catfish does that, vector<Big>::reserve() will use move ctors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Big(Big &&b) noexcept : pl(b.pl), s(b.s) {

...

int main() {
    std::vector<Big> v(2, createBig());
    v.reserve(10);
}
Big(2, 13)
Little()
Little()
Little(13)
Little & operator = (const Little &)
Little & operator = (const Little &)
~Little()
Big(const Big &)
Little()
Little()
Little & operator = (const Little &)
Little & operator = (const Little &)
Big(const Big &)
Little()
Little()
Little & operator = (const Little &)
Little & operator = (const Little &)
~Big()
~Little()
~Little()
Big(Big &&)
Big(Big &&)
~Big()
~Big()
~Big()
~Little()
~Little()
~Big()
~Little()
~Little()


Note the two calls to Big(Big &&): they were made by vector::reserve() when it relocated the two Bigs from the initial memory chunk to the bigger one
Last edited on
To whom it may concern, noexcept is not available in VS2010.
Seems to be working anyway, though.

Code:
http://pastebin.com/wkNUB0aE

Output:
Big createBig()
Big(2, 13)
Little()
Little()
Little(13)
Little & operator = (const Little &)
Little & operator = (const Little &)
~Little()
Big(const Big &)
Little()
Little()
Little & operator = (const Little &)
Little & operator = (const Little &)
Big(const Big &)
Little()
Little()
Little & operator = (const Little &)
Little & operator = (const Little &)
~Big()
~Little()
~Little()
Big(Big &&)
Big(Big &&)
~Big()
~Big()
~Big()
~Little()
~Little()
~Big()
~Little()
~Little()
> noexcept is not available in VS2010.
> Seems to be working anyway, though.

The compiler for VS2010 was written before this issue came under feverish discussion within the C++ community; before N2983 was proposed as the solution; before update 2 was added to this article:
http://cpp-next.com/archive/2009/10/exceptionally-moving/

As far as move semantics with standard containers and deduced move semantics in classes with defaulted foundation operations are concerned, VS2010 is a non-conforming implementation.
Topic archived. No new replies allowed.