File: rvalueref.yo

package info (click to toggle)
c%2B%2B-annotations 13.02.02-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,576 kB
  • sloc: cpp: 25,297; makefile: 1,523; ansic: 165; sh: 126; perl: 90; fortran: 27
file content (160 lines) | stat: -rw-r--r-- 7,052 bytes parent folder | download | duplicates (3)
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
In bf(C++), temporary (rvalue) values are indistinguishable from tt(const &)
types. bf(C++) introduces a new reference type called an
    emi(rvalue reference), which is defined as ti(typename &&).

The name em(rvalue) reference is derived from assignment statements, where the
variable to the left of the assignment operator is called an emi(lvalue) and
the expression to the right of the assignment operator is called an
emi(rvalue). Rvalues are often temporary, anonymous values, like values
returned by functions.

In this parlance the bf(C++) reference should be considered an
 emi(lvalue reference) (using the notation tt(typename &)). They can be
contrasted to em(rvalue references) (using the notation tt(typename &&)).

The key to understanding rvalue references is the concept of an
 emi(anonymous variable). An anonymous variable has no name and this is the
distinguishing feature for the compiler to associate it automatically with an
rvalue reference if it has a choice. Before introducing some interesting
constructions let's first have a look at some standard situations where
em(lvalue) references are used. The following function returns a temporary
(anonymous) value:
        verb(    int intVal()
    {
        return 5;
    })

Although tt(intVal)'s return value  can be assigned to an tt(int)
variable it requires copying, which might become prohibitive when
a function does not return an tt(int) but instead some large object. A
em(reference) or em(pointer) cannot be used either to collect the anonymous
return value as the return value won't survive beyond that. So the following
is illegal (as noted by the compiler):
        verb(    int &ir = intVal();         // fails: refers to a temporary
    int const &ic = intVal();   // OK: immutable temporary
    int *ip = &intVal();        // fails: no lvalue available)

Apparently it is not possible to modify the temporary returned by
tt(intVal). But now consider these functions:
        verb(    void receive(int &value)            // note: lvalue reference
    {
        cout << "int value parameter\n";
    }
    void receive(int &&value)           // note: rvalue reference
    {
        cout << "int R-value parameter\n";
    })

and let's call this function from tt(main):
        verb(    int main()
    {
        receive(18);
        int value = 5;
        receive(value);
        receive(intVal());
    })

This program produces the following output:
        verb(    int R-value parameter
    int value parameter
    int R-value parameter)

The program's output shows the compiler selecting tt(receive(int &&value))
in all cases where it receives an anonymous tt(int) as its argument. Note that
this includes tt(receive(18)): a value 18 has no name and thus tt(receive(int
&&value)) is called. Internally, it actually uses a temporary variable to
store the 18, as is shown by the following example which modifies tt(receive):
        verb(    void receive(int &&value)
    {
        ++value;
        cout << "int R-value parameter, now: " << value << '\n';
            // displays 19 and 6, respectively.
    })

Contrasting tt(receive(int &value)) with tt(receive(int &&value)) has
nothing to do with tt(int &value) not being a const reference. If
tt(receive(int const &value)) is used the same results are obtained. Bottom
line: the compiler selects the overloaded function using the rvalue reference
if the function is passed an anonymous value.

    The compiler runs into problems if tt(void receive(int &value)) is
replaced by tt(void receive(int value)), though. When confronted with the
choice between a value parameter and a reference parameter (either lvalue or
rvalue) it cannot make a decision and reports an ambiguity. In practical
contexts this is not a problem. Rvalue references were added to the language in
order to be able to distinguish the two forms of references: named values
(for which lvalue references are used) and anonymous values (for which
rvalue references are used).

    It is this distinction that allows the implementation of
 emi(move semantics) and emi(perfect forwarding). At this point the concept of
emi(move semantics) cannot yet fully be discussed (but see section ref(MOVE)
for a more thorough discussion) but it is very well possible to illustrate
the underlying ideas.

Consider the situation where a function returns a tt(struct Data) containing a
pointer to a dynamically allocated NTBS. We agree that tt(Data) objects
are only used after initialization, for which two tt(init) functions
are available. As an aside: when tt(Data) objects are no longer required the
memory pointed at by tt(text) must again be returned to the operating
system; assume that that task is properly performed.
        verb(    struct Data
    {
        char *text;

        void init(char const *txt);     // initialize text from txt

        void init(Data const &other)
        {
            text = strdup(other.text);
        }
    };)

There's also this interesting function:
        verb(    Data dataFactory(char const *text);)

Its implementation is irrelevant, but it returns a (temporary) tt(Data)
object initialized with tt(text). Such temporary objects cease to exist once
the statement in which they are created end.

Now we'll use tt(Data):
        verb(    int main()
    {
        Data d1;
        d1.init(dataFactory("object"));
    })

Here the tt(init) function duplicates the NTBS stored in the temporary
object. Immediately thereafter the temporary object ceases to exist. If you
think about it, then you realize that that's a bit over the top:
    itemization(
    it() the tt(dataFactory) function uses tt(init) to initialize the tt(text)
        variable of its temporary tt(Data) object. For that it uses
        tt(strdup); 
    it() the tt(d1.init) function then em(also) uses tt(strdup) to initialize
        tt(d1.text); 
    it() the statement ends, and the temporary object ceases to exist.
    )
    That's two tt(strdup) calls, but the temporary tt(Data) object thereafter
is never used again.

    To handle cases like these em(rvalue reference) were introduced. We add
the following function to the tt(struct Data):
        verb(    void init(Data &&tmp)
    {
        text = tmp.text;      // (1)
        tmp.text = 0;         // (2)
    })

Now, when the compiler translates tt(d1.init(dataFactory("object"))) it
notices that tt(dataFactory) returns a (temporary) object, and because of that
it uses the tt(init(Data &&tmp)) function. As we know that the tt(tmp) object
ceases to exist after executing the statement in which it is used, the tt(d1)
object (at (1)) em(grabs) the temporary object's tt(text) value, and then (at
(2)) assigns 0 to tt(other.text) so that the temporary object's tt(free(text))
action does no harm.

Thus, tt(struct Data) suddenly has become emi(move-aware) and implements
em(move semantics), removing the (extra copy) drawback of the previous
approach, and instead of making an extra copy of the temporary object's NTBS
the pointer value is simply transferred to its new owner.