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
|
Our class tt(Strings) has, among other members, a data member tt(string
*d_string). Clearly, tt(Strings) should define a copy constructor, a
destructor and an overloaded assignment operator.
Now consider the following function tt(loadStrings(std::istream &in))
extracting the strings for a tt(Strings) object from tt(in). Next, the
tt(Strings) object filled by tt(loadStrings) is returned by value. The
function tt(loadStrings) returns a temporary object, which can then used to
initialize an external tt(Strings) object:
verb( Strings loadStrings(std::istream &in)
{
Strings ret;
// load the strings into 'ret'
return ret;
}
// usage:
Strings store(loadStrings(cin));)
In this example two full copies of a tt(Strings) object are required:
itemization(
it() initializing tt(loadString)'s value return type from its local
tt(Strings ret) object;
it() initializing tt(store) from tt(loadString)'s return value
)
We can improve the above procedure by defining a
emi(move constructor). Here is the declaration of the tt(Strings) class move
constructor:
verb( Strings(Strings &&tmp);)
Move constructors of classes using dynamic memory allocation are allowed
to assign the values of pointer data members to their own pointer data members
without requiring them to make copies of the source's data. Next, the
temporary's pointer value is set to zero to prevent its destructor from
destroying data now owned by the just constructed object. The move constructor
has em(grabbed) or
hi(resource: stealing) em(stolen) the data from the temporary object. This is
OK as the temporary object cannot be referred to again (as it is anonymous, it
cannot be accessed by other code) and we may assume that the temporary objects
cease to exist shortly after the move-constructor's call. Here is an
implementation of tt(Strings) move constructor:
verbinclude(-a examples/stringsmove.cc)
Move construction (in general: moving) must leave the object from which
information was moved in a valid state. It is not specified in what way that
valid state must be realized, but a good i(rule of thumb) is to return the
object to its default constructed state. In the above illustration of a move
constructor tt(tmp.d_size, tmp.d_capacity) and tt(tmp.d_string) are all set to
0. It's up to the author of the tt(Strings) class to decide whether or not
the tt(String's) members can all be set to 0. If there's a default constructor
doing exactly that then assigning zeroes is fine. If tt(d_capacity) is doubled
once tt(d_size == d_capacity) then setting tt(d_capactiy) to 1 and letting
tt(d_string) point to a newly allocated block of raw memory the size of a
tt(string) might be attractive. The source object's capacity might even
remain as-is. E.g., when moving tt(std::string) objects the source object's
capacity isn't altered.
In section ref(COPYCONS) it was stated that the copy constructor is almost
hi(copy constructor: suppressed)
always available. em(Almost) always as the declaration of a move constructor
suppresses the default availability of the copy constructor. The default copy
constructor is also suppressed if a em(move assignment operator) is declared
(cf. section ref(MOVEASS)).
The following example shows a simple class tt(Class), declaring a move
constructor. In the tt(main) function following the class interface a
tt(Class) object is defined which is then passed to the constructor of a
second tt(Class) object. Compilation fails with the compiler reporting:
verb( error: cannot bind 'Class' lvalue to 'Class&&'
error: initializing argument 1 of 'Class::Class(Class&&)')
verbinclude(-a nocopycons.cc)
The cure is easy: after declaring a (possibly tt(default)) copy
constructor the error disappears:
verbinclude(-a examples/copycons.cc)
|