File: reference.yo

package info (click to toggle)
c%2B%2B-annotations 13.04.01-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,636 kB
  • sloc: cpp: 25,471; makefile: 1,521; ansic: 165; sh: 126; perl: 90; fortran: 27
file content (146 lines) | stat: -rw-r--r-- 6,825 bytes parent folder | download | duplicates (2)
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
We've seen that binary operators (like tt(operator+)) can be implemented very
efficiently, but require at least move constructors.

An expression like
        verb(    Binary{} + varB + varC + varD)

therefore returns a move constructed object representing tt(Binary{} +
varB), then another move constructed object receiving the first return value
and tt(varC), and finally yet another move constructed object receiving the
second returned object and tt(varD) as its arguments.

Now consider the situation where we have a function defining a tt(Binary &&)
parameter, and a second tt(Binary const &) parameter. Inside that function
these values need to be added, and their sum is then passed as argument to two
other functions. We em(could) do this:
        verb(    void fun1(Binary &&lhs, Binary const &rhs)
    {
        lhs += rhs;
        fun2(lhs);
        fun3(lhs);
    })

But realize that when using tt(operator+=) we first construct a copy of
the current object, so a temporary object is available to perform the addition
on, and then swap the temporary object with the current object to commit the
results. But wait! Our lhs operand already em(is) a temporary object. So why
create another?

    In this example another temporary object is indeed not required: tt(lhs)
remains in existence until tt(fun1) ends. But different from the binary
operators the binary compound assignment operators don't have explicitly
defined left-hand side operands. But we still can inform the compiler that a
particular em(member) (so, not merely compound assignment operators) should
only be used when the objects calling those members is an anonymous temporary
object, or a non-anonymous (modifiable or non-modifiable) object. For this
we use
    em(reference bindings)hi(reference binding) a.k.a.  
    em(reference qualifiers)hi(reference qualifier). 

    Reference bindings consist of a reference token (tt(&)), optionally
preceded by tt(const), or an rvalue reference token (tt(&&)). Such reference
qualifiers are immediately affixed to the function's head (this applies to the
declaration and the implementation alike). Functions provided with rvalue
reference bindings are selected by the compiler when used by anonymous
temporary objects, whereas functions provided with lvalue reference bindings
are selected by the compiler when used by other types of objects.

    Reference qualifiers allow us to fine-tune our implementations of compound
assignment operators like tt(operator+=). If we know that the object calling
the compound assignment operator is itself a temporary, then there's no need
for a separate temporary object. The operator may directly perform its
operation and could then return itself as an rvalue reference. Here is the
implementation of tt(operator+=) tailored to being used by temporary objects:
        verb(    Binary &&Binary::operator+=(Binary const &rhs) &&
    {
        add(rhs);                   // directly add rhs to *this, 
        return std::move(*this);    // return the temporary object itself
    })

This implementation is about as fast as it gets. But be careful: in the
previous section we learned that a temporary is destroyed at the end of the
full expression of a return stattement. In this case, however, the temporary
already exists, and so (also see the previous section) it should persist until
the expression containing the (tt(operator+=)) function call is completed. As
a consequence,
            verb(    cout << (Binary{} += existingBinary) << '\n';)

is OK, but
            verb(    Binary &&rref = (Binary{} += existingBinary);
    cout << rref << '\n';)

is not, since tt(rref) becomes a dangling reference immediately after its
initialization. 

    A full-proof alternative implementation of the rvalue-reference bound
tt(operator+=) returns a move-constructed copy:
        verb(    Binary Binary::operator+=(Binary const &rhs) &&
    {
        add(rhs);                   // directly add rhs to *this, 
        return std::move(*this);    // return a move constructed copy
    })

The price to pay for this full-proof implementation is an extra move
construction. Now, using the previous example (using tt(rref)), tt(operator+=)
returns a copy of the tt(Binary{}) temporary, which is still a temporary
object which can safely be referred to by tt(rref).

    Which implementation to use may be a matter of choice: if users of
tt(Binary) know what they're doing then the former implementation can be used,
since these users will never use the above tt(rref) initialization. If you're
not so sure about your users, use the latter implementation: formally your
users will do something they shouldn't do, but there's no penalty for that.

    For the compound assignment operator called by an lvalue reference (i.e.,
a named object) we use the implementation for tt(operator+=) from the previous
section (note the reference qualifier):
        verb(    Binary &Binary::operator+=(Binary const &rhs) &
    {
        Binary tmp(*this);
        tmp.add(rhs);           // this might throw
        swap(tmp);
        return *this;
    })

With this implementation adding tt(Binary) objects to each other
(e.g., tt(b1 += b2 += b3)) boils down to
        verb(    operator+=    (&)       = b2 += b3
    Copy constructor        = tmp(b2) 
        adding              = tmp.add(b3)
        swap                = b2 <-> tmp
    return                  = b2

    operator+=    (&)       = b1 += b2
    Copy constructor        = tmp(b1) 
        adding              = tmp.add(b2)
        swap                = b1 <-> tmp
    return                  = b1)

When the leftmost object is a temporary then a copy construction and swap
call are replaced by the construction of an anonymous object. E.g.,
with tt(Binary{} += b2 += b3) we observe:
        verb(    operator+=    (&)       = b2 += b3
    Copy constructor        = tmp(b2) 
        adding              = tmp.add(b3)
        swap                = b2 <-> tmp
    
    Anonymous object        = Binary{}

    operator+=    (&&)      = Binary{} += b2
        adding              = add(b2)

    return                  = move(Binary{}))

For tt(Binary &Binary::operator+=(Binary const &rhs) &) an alternative
implementation exists, merely using a single return statement, but in fact
requiring two extra function calls. It's a matter of taste whether you prefer
writing less code or executing fewer function calls:  
        verb(    Binary &Binary::operator+=(Binary const &rhs) &
    {
        return *this = Binary{ *this } += rhs;
    })

Notice that the implementations of tt(operator+) and tt(operator+=) are
independent of the actual definition of the class tt(Binary). Adding standard
binary operators to a class (i.e., operators operating on arguments of their
own class types) can therefore easily be realized.