Movatterモバイル変換


[0]ホーム

URL:




Chapter 9: Classes And Memory Allocation

In contrast to the set of functions that handlememory allocation inC(i.e.,malloc etc.), memory allocation inC++ is handled bythe operatorsnew anddelete.Important differences betweenmalloc andnew are: A comparable relationship exists betweenfree anddelete:delete makes sure that when an object is deallocated, itsdestructor is automatically called.

The automatic calling of constructors and destructors when objects are createdand destroyed has consequences which we shall discuss in this chapter. Manyproblems encountered duringC program development are caused by incorrectmemory allocation ormemory leaks: memory is not allocated, notfreed, not initialized, boundaries are overwritten, etc..C++ does not`magically' solve these problems, but itdoes provide us with tools toprevent these kinds of problems.

As a consequence ofmalloc and friends becoming deprecatedthe very frequently usedstr... functions, likestrdup, that are allmalloc based, should be avoided inC++ programs. Instead, the facilities of thestring class andoperatorsnew anddelete should be used.

Memory allocation procedures influence the way classes dynamically allocatingtheir own memory should be designed. Therefore, in this chapter these topicsare discussed in addition to discussions about operatorsnew anddelete. We'll first cover the peculiarities of operatorsnew anddelete, followed by a discussion about:

9.1: Operators `new' and `delete'

C++ defines two operators to allocate memory and to return it to the`common pool'. These operators are, respectively,new anddelete.

Here is a simple example illustrating their use. Anint pointer variablepoints to memory allocated by operatornew. This memory is later releasedby operatordelete.

    int *ip = new int;    delete ip;

Here are some characteristics of operatorsnew anddelete:

Operatornew can be used toallocate primitive types but also toallocate objects. When a primitive typeor astruct type without a constructor is allocated the allocatedmemory isnot guaranteed to be initialized to 0, but aninitialization expression may be provided:

    int *v1 = new int;          // not guaranteed to be initialized to 0    int *v1 = new int();        // initialized to 0    int *v2 = new int(3);       // initialized to 3    int *v3 = new int(3 * *v2); // initialized to 9

When a class-type object is allocated, the arguments of its constructor(if any) are specified immediately following the type specification in thenew expression and the object is initialized by to the thus specifiedconstructor. For example, to allocatestring objects the followingstatements could be used:

    string *s1 = new string;            // uses the default constructor    string *s2 = new string{};          // same    string *s3 = new string(4, ' ');    // initializes to 4 blanks.

In addition to usingnew to allocate memory for a single entity or anarray of entities (see the next section) there also exists a variantallocatingraw memory:operator new(sizeInBytes). Raw memory is returned as avoid *. Herenew allocates a block of memory for unspecified purpose. Although rawmemory may consist of multiple characters it should not be interpreted as anarray of characters. Since raw memory returned bynew is returned as avoid * its return value can be assigned to avoid * variable. Moreoften it is assigned to achar * variable, using a cast. Here is anexample:

    char *chPtr = static_cast<char *>(operator new(numberOfBytes));

The use of raw memory is frequently encountered in combination with theplacement new operator, discussed in section9.1.5.

9.1.1: Allocating arrays

Operatornew[] is used toallocate arrays. The generic notationnew[] is used in theC++ Annotations. Actually, the number of elements to beallocated must be specified between the square brackets and it must, in turn,beprefixed by the type of the entities that must be allocated. Example:
    int *intarr = new int[20];          // allocates 20 ints    string *stringarr = new string[10]; // allocates 10 strings.

Operatornew is a different operator than operatornew[]. Aconsequence of this difference is discussed in the next section(9.1.2).

Arrays allocated by operatornew[] are calleddynamic arrays. They are constructed during theexecution of a program, and their lifetime may exceed the lifetime of thefunction in which they were created. Dynamically allocated arrays may last foras long as the program runs.

Whennew[] is used to allocate an array of primitive values or anarray of objects,new[] must be specified with a type and an (unsigned)expression between its square brackets. The type and expression together areused by the compiler to determine the required size of the block of memory tomake available. Whennew[] is used the array's elements are storedconsecutively in memory. An array index expression may thereafter be used toaccess the array's individual elements:intarr[0] represents the firstint value, immediately followed byintarr[1], and so on until the lastelement (intarr[19]).

With non-class types (primitive types, PODtypes without constructors) the block of memory returned by operatornew[]isnot guaranteed to be initialized to 0. Alternatively, adding() tothenew expressionwill initialize the block of memory tozeroes. E.g.,

    struct POD    {        int iVal;        double dVal;    };    new POD[5]();       // returns a pointer to 5 0-initialized PODs    new double[9]();    // returns a pointer to 9 0-initialized doubles

In addition to usingnew Type[size](), the array's size can also beinferred by the compiler by specifying comma-separated initialization valuesbetween the parentheses, likenew int[](1, 2, 3). This allocates an arrayof threeints which are initialized to, respectively, the values 1, 2 and3. Specifying a larger value between the square brackets is also possible. Inthat case the first values are initialized as specified, the remaining valuesare initialized to zeroes (or by the objects' default constructors). Insteadof using parentheses curly braces can also be used.

If there are members of thestruct POD that are explicitly initializedin the struct's interface (e.g.,int iVal = 12), or if the struct usescomposition, and the composed data member's type defines a defaultconstructor, then initializations in the struct's interface andinitializations performed by the composed data member's constructor takesprecedence over the 0-initialization. Here is an example:

    struct Data    {        int value = 100;    };    struct POD    {        int iVal = 12;        double dVal;        Data data;    };    POD *pp = new POD[5]();

Here,pp points to fivePOD objects, each having theiriValdata members initialized to 12, theirdVal data members initialized to 0,and theirdata.value members initialized to 100.

When operatornew[] is used to allocate arrays of objects of classtypes defining default constructors these constructors are automaticallyused. Consequentlynew string[20] results in a block of 20initializedstring objects. A non-default constructor cannot be called, but often itis possible to work around that (as discussed in section13.8).

The expression between brackets of operatornew[] represents thenumber of elements of the array to allocate. TheC++ standard allowsallocation of 0-sized arrays. The statementnew int[0] is correctC++. However, it is alsopointless and confusing and should be avoided. It is pointless as it doesn'trefer to any element at all, it is confusing as the returned pointer has auseless non-0 value. A pointer intending to point to an array of values shouldbe initialized (like any pointer that isn't yet pointing to memory) to 0,allowing for expressions likeif (ptr) ...

Without using operatornew[], arrays of variable sizes can also beconstructed aslocal arrays. Such arrays are not dynamic arrays andtheir lifetimes are restricted to the lifetime of the block in which they weredefined.

Once allocated, all arrays have fixed sizes. There is nosimple way to enlarge or shrink arrays.C++ has no operator`renew'. Section9.1.3 illustrates how toenlarge arrays.

9.1.2: Deleting arrays

Dynamically allocated arrays are deleted using operatordelete[]. It expects a pointer to a block of memory, previously allocatedby operatornew[].

When operatordelete[]'s operand is a pointer to an array of objectstwo actions are performed:

Here is an example showing how to allocate and delete an array of 10string objects:
    std::string *sp = new std::string[10];    delete[] sp;

No special action is performed if a dynamically allocated array ofprimitive typed values is deleted. Followingint *it = new int[10] thestatementdelete[] it simply returns the memory pointed at byit.Realize that, as a pointer is a primitive type, deleting a dynamicallyallocated array of pointers to objects doesnot result in the properdestruction of the objects the array's elements point at. So, the followingexample results in amemory leak:

    string **sp = new string *[5];    for (size_t idx = 0; idx != 5; ++idx)        sp[idx] = new string;    delete[] sp;            // MEMORY LEAK !

In this example the only action performed bydelete[] is to return anarea the size of five pointers to strings to the common pool.

Here's how the destruction in such casesshould be performed:

Example:
    for (size_t idx = 0; idx != 5; ++idx)        delete sp[idx];    delete[] sp;

One of the consequences is of course that by the time the memory is goingto be returned not only the pointer must be available but also the number ofelements it contains. This can easily be accomplished by storing pointer andnumber of elements in a simple class and then using an object of that class.

Operatordelete[] is a different operator than operatordelete. Therule of thumb is: ifnew[] was used, also usedelete[].

9.1.3: Enlarging arrays

Once allocated, all arrays have fixed sizes.There is no simple way to enlarge or shrink arrays.C++ has norenew operator. The basic steps to take when enlarging an array are thefollowing: Static and local arrays cannot be resized. Resizing is only possible fordynamically allocated arrays. Example:
    #include <string>    using namespace std;    string *enlarge(string *old, size_t oldsize, size_t newsize)    {        string *tmp = new string[newsize];  // allocate larger array        for (size_t idx = 0; idx != oldsize; ++idx)            tmp[idx] = old[idx];            // copy old to tmp        delete[] old;                       // delete the old array        return tmp;                         // return new array    }    int main()    {        string *arr = new string[4];        // initially: array of 4 strings        arr = enlarge(arr, 4, 6);           // enlarge arr to 6 elements.    }

The procedure to enlarge shown in the example also has several drawbacks.

Depending on the context various solutions exist to improve the efficiencyof this rather inefficient procedure. An array of pointers could be used(requiring only the pointers to be copied, no destruction, no superfluousinitialization) or raw memory in combination with theplacement new operator could be used (an array of objectsremains available, no destruction, no superfluous construction).

9.1.4: Managing `raw' memory

As we've seen operatornew allocates the memory for an object andsubsequently initializes that object by calling one of itsconstructors. Likewise, operatordelete calls an object's destructor andsubsequently returns the memory allocated by operatornew to the commonpool.

In the next section we'll encounter another use ofnew, allowing us toinitialize objects in so-calledraw memory: memory merely consisting ofbytes that have been made available by either static or dynamic allocation.

Raw memory is made available byoperator new(sizeInBytes) and also byoperatornew[](sizeInBytes). The returned memory should not be interpreted as an arrayof any kind but just a series of memory locations that were dynamically madeavailable. No initialization whatsoever is performed by these variants ofnew.

Both variants returnvoid *s so (static) casts are required to use thereturn values as memory of some type.

Here are two examples:

                                // room for 5 ints:    int *ip = static_cast<int *>(operator new(5 * sizeof(int)));                                // same as the previous example:    int *ip2 = static_cast<int *>(operator new[](5 * sizeof(int)));                                // room for 5 strings:    string *sp = static_cast<string *>(operator new(5 * sizeof(string)));

Asoperator new has no concept of data types the size of the intendeddata type must be specified when allocating raw memory for a certain number ofobjects of an intended type. The use ofoperator new therefore somewhatresembles the use ofmalloc.

The counterpart ofoperator new isoperator delete.Operatordelete (or, equivalently,operator delete[]), expects avoid * (so apointer to any type can be passed to it). The pointer is interpreted as apointer to raw memory which is returned to the common pool without any furtheraction. In particular, no destructors are called byoperator delete. Theuse ofoperator delete therefore resembles the use offree. To returnthe memory pointed at by the abovementioned variablesip andspoperator delete should be used:

                    // delete raw memory allocated by operator new    operator delete(ip);    operator delete[](ip2);    operator delete(sp);

9.1.5: The `placement new' operator

A remarkable form of operatornew is called theplacement new operator. Before using placementnew the<memory>header file must be included.

Placementnew is passed an existing block of memory into whichnewinitializes an object or value. The block of memory should be large enough tocontain the object, but apart from that there are no further requirements. Itis easy to determine how much memory is used by an entity (object or variable)of typeType: thesizeof operator returns the number of bytes used by anType entity.

Entities may of course dynamically allocate memory for their own use.Dynamically allocated memory, however, is not part of the entity's memory`footprint' but it is always made available externally to the entityitself. This is whysizeof returns the same value when applied todifferentstring objects that return different length and capacity values.

The placementnew operator uses the following syntax (usingType toindicate the used data type):

    Type *new(void *memory) Type{ arguments };

Here,memory is a block of memory of at leastsizeof(Type) bytesandType(arguments) is any constructor of the classType.

The placementnew operator is useful in situations where classes setaside memory to be used later. This is used, e.g., bystd::string tochange its capacity. Callingstring::reserve may enlarge that capacitywithout making memory beyond the string's length immediately available to thestring object's users. But the object itselfmay use its additionalmemory. E.g, when information is added to astring object it can drawmemory from its capacity rather than performing a reallocation for each singlecharacter that is added to its content.

Let's apply that philosophy to a classStrings storingstd::stringobjects. The class defines astring *d_memory accessing the memory holdingitsd_size string objects as well asd_capacity - d_size reservedmemory. Assuming that a default constructor initializesd_capacity to 1,doublingd_capacity whenever an additionalstring must be stored, theclass must support the following essential operations:

The private membervoid Strings::reserve is called when the currentcapacity must be enlarged tod_capacity. It operates as follows: First new, raw, memory is allocated (line 1). This memory is in no wayinitialized with strings. Then the available strings in the old memory arecopied into the newly allocated raw memory using placement new (line 2). Next,the old memory is deleted (line 3).
void Strings::reserve(){    using std::string;    string *newMemory = static_cast<string *>(                  // 1                            operator new(d_capacity * sizeof(string)));    for (size_t idx = 0; idx != d_size; ++idx)                  // 2        new (newMemory + idx) string{ d_memory[idx] };    destroy();                                                  // 3    d_memory = newMemory;}

The memberappend adds anotherstring object to aStringsobject. A (public) memberreserve(request) (enlargingd_capacity ifnecessary and if enlarged callingreserve()) ensures that theStringobject's capacity is sufficient. Then placementnew is used to install thelatest string into the raw memory's appropriate location:

void Strings::append(std::string const &next){    reserve(d_size + 1);    new (d_memory + d_size) std::string{ next };    ++d_size;}

At the end of theString object's lifetime, and during enlargingoperations all currently used dynamically allocated memory must bereturned. This is made the responsibility of the memberdestroy, which iscalled by the class's destructor and byreserve(). More about thedestructor itself inthe next section, but theimplementation of the support memberdestroy is discussed below.

With placementnew an interesting situation is encountered. Objects,possibly themselves allocating memory, are installed in memory that may or maynot have been allocated dynamically, but that is usually not completely filledwith such objects. So a simpledelete[] can't be used. On the other hand,adelete for each of the objects thatare available can't be usedeither, since thosedelete operations would also try to delete the memoryof the objects themselves, which wasn't dynamically allocated.

This peculiar situation is solved in a peculiar way, only encountered incases where placementnew is used: memory allocated by objects initializedusing placementnew is returned byexplicitly calling the object's destructor. The destructor isdeclared as a member having as its name the class name preceded by a tilde,not using any arguments. So,std::string's destructor is named~string. An object's destructoronly returns memory allocated by theobject itself and, despite of its name, doesnot destroy its object. Anymemory allocated by thestrings stored in our classStrings istherefore properly destroyed by explicitly calling theirdestructors. Following thisd_memory is back to its initial status: itagain points to raw memory. This raw memory is then returned to the commonpool byoperator delete:

void Strings::destroy(){    for (std::string *sp = d_memory + d_size; sp-- != d_memory; )        sp->~string();    operator delete(d_memory);}

So far, so good. All is well as long as we're using only one object. Whatabout allocating an array of objects? Initialization is performed as usual.But as withdelete,delete[] cannot be called when the buffer wasallocated statically. Instead, when multiple objects were initialized usingplacementnew in combination with a statically allocatedbuffer all the objects' destructors must be called explicitly, as in thefollowing example:

    using std::string;    char buffer[3 * sizeof(string)];    string *sp = new(buffer) string [3];    for (size_t idx = 0; idx < 3; ++idx)        sp[idx].~string();

Several standard template library functions are available for handlingunitialized (raw) memory. See section19.1.58 for a description.

9.2: The destructor

Comparable to the constructor, classes may define adestructor. Thisfunction is the constructor's counterpart in the sense that it is invoked whenan object ceases to exist. A destructor is usually called automatically, butthat's not always true. The destructors of dynamically allocated objects arenot automatically activated, but in addition to that: when a program isinterrupted by anexit call, only the destructors ofalready initialized global objects are called. In that situation destructorsof objects definedlocally by functions are alsonot called. This isone (good) reason for avoidingexit inC++ programs.

Destructors obey the following syntactical requirements:

Destructors are declared in their class interfaces. Example:
    class Strings    {        public:            Strings();            ~Strings();     // the destructor    };

By convention the constructors are declared first. The destructor isdeclared next, to be followed by other member functions.

A destructor's main task is to ensure thatmemory allocated by an object is properly returned when the object ceases toexist. Consider the following interface of the classStrings:

    class Strings    {        std::string *d_string;        size_t d_size;        public:            Strings();            Strings(char const *const *cStrings, size_t n);            ~Strings();            std::string const &at(size_t idx) const;            size_t size() const;    };

The constructor's task is to initialize the data fields of the object. E.g,its constructors are defined as follows:

    Strings::Strings()    :        d_string(0),        d_size(0)    {}    Strings::Strings(char const *const *cStrings, size_t size)    :        d_string(new string[size]),        d_size(size)    {        for (size_t idx = 0; idx != size; ++idx)            d_string[idx] = cStrings[idx];    }
As objects of the classStrings allocate memory a destructor isclearly required. Destructors may or may not be called automatically, but notethat destructors areonly called (or, in the case of dynamically allocatedobjects: should only be called) for fully constructed objects.

C++ considers objects `fully constructed' once at least one of itsconstructors could normally complete. It used to bethe constructor, butasC++ supports constructor delegation, multiple constructors can beactivated for a single object; hence `at least one constructor'. Theremaining rules apply to fully constructed objects;

The destructor's task is to ensure that all memory that isdynamically allocated and controlled only by the objectitself is returned. The task of theStrings'sdestructor would therefore be to delete the memory to whichd_stringpoints. Its implementation is:
    Strings::~Strings()    {        delete[] d_string;    }

The next example showsStrings at work. InprocessaStrings store is created, and its data are displayed. It returns adynamically allocatedStrings object tomain. AStrings * receives the address of the allocated object and deletes theobject again. AnotherStrings object is then created in a block ofmemory made available locally inmain, and anexplicit call to~Strings is requiredto return the memory allocated by that object. In the example only once aStrings object is automatically destroyed: the localStringsobject defined byprocess. The other twoStrings objects requireexplicit actions to prevent memory leaks.

    #include "strings.h"    #include <iostream>    using namespace std;;    void display(Strings const &store)    {        for (size_t idx = 0; idx != store.size(); ++idx)            cout << store.at(idx) << '\n';    }    Strings *process(char *argv[], size_t argc)    {        Strings store{ argv, argc };        display(store);        return new Strings{ argv, argc };    }    int main(int argc, char *argv[])    {        Strings *sp = process(argv, argc);        delete sp;        char buffer[sizeof(Strings)];        sp = new (buffer) Strings{ argv, static_cast<size_t>(argc) };        sp->~Strings();    }

9.2.1: Object pointers revisited

Operatorsnew anddelete are used when an object or variable isallocated. One of the advantages of the operatorsnew anddelete over functions likemalloc andfree is thatnew anddelete call the corresponding object constructors and destructors.

The allocation of an object by operatornew is a two-stepprocess. First the memory for the object itself is allocated. Then itsconstructor is called, initializing the object. Analogously to theconstruction of an object, the destruction is also a two-step process: first,the destructor of the class is called deleting the memory controlled by theobject. Then the memory used by the object itself is freed.

Dynamically allocated arrays of objects can also be handled bynew anddelete. When allocating an array of objects using operatornew thedefault constructor is called for each object in the array. In cases like thisoperatordelete[] must be used to ensure that the destructor is called foreach of the objects in array.

However, the addresses returned bynew Type andnew Type[size]are of identical types, in both cases aType *. Consequently it cannot bedetermined by the type of the pointer whether a pointer to dynamicallyallocated memory points to a single entity or to an array of entities.

What happens ifdelete rather thandelete[] is used? Consider thefollowing situation, in which the destructor~Strings is modified so thatit tells us that it is called. In amain function an array of twoStrings objects is allocated usingnew, to be deleted bydelete[]. Next, the same actions are repeated, albeit that thedelete operator iscalled without[]:

    #include <iostream>    #include "strings.h"    using namespace std;    Strings::~Strings()    {        cout << "Strings destructor called" << '\n';    }    int main()    {        Strings *a  = new Strings[2];        cout << "Destruction with []'s" << '\n';        delete[] a;        a = new Strings[2];        cout << "Destruction without []'s" << '\n';        delete a;    }/*    Generated output:Destruction with []'sStrings destructor calledStrings destructor calledDestruction without []'sStrings destructor called*/
From the generated output, we see that the destructors of the individualStrings objects are called whendelete[] is used, while only thefirst object's destructor is called if the[] is omitted.

Conversely, ifdelete[] is called in a situation wheredeleteshould have been called the results are unpredictable, and the program willmost likely crash. This problematic behavior is caused by the way the run-timesystem stores information about the size of the allocated array (usually rightbefore the array's first element). If a single object is allocated thearray-specific information is not available, but it is nevertheless assumedpresent bydelete[]. Thus this latter operator encounters bogus values inthe memory locations just before the array's first element. It then dutifullyinterprets the value it encounters there as size information, usually causingthe program to fail.

If no destructor is defined, atrivial destructor is defined by thecompiler. The trivial destructor ensures that the destructors of composedobjects (as well as the destructors ofbase classes if a class is aderived class, cf. chapter13) are called. This has seriousimplications: objects allocating memory create memory leaksunless precautionary measures are taken (by defining an appropriatedestructor). Consider the following program:

    #include <iostream>    #include "strings.h"    using namespace std;    Strings::~Strings()    {        cout << "Strings destructor called" << '\n';    }    int main()    {        Strings **ptr = new Strings* [2];        ptr[0] = new Strings[2];        ptr[1] = new Strings[2];        delete[] ptr;    }
This program produces no output at all. Why is this? The variableptris defined as a pointer to a pointer. The dynamically allocated arraytherefore consists of pointer variables and pointers are of a primitive type.No destructors exist for primitive typed variables. Consequently only thearray itself is returned, and noStrings destructor is called.

Of course, we don't want this, but require theStrings objectspointed to by the elements ofptr to be deleted too. In this case we havetwo options:

9.2.2: The function set_new_handler()

TheC++ run-time system ensures that when memory allocation fails anerror function is activated. By default this function throws abad_alloc exception (see section10.8), terminating the program. Therefore it is not necessary to checkthe return value of operatornew. Operatornew's default behavior maybe modified in various ways. One way to modify its behavior is to redefine thefunction that's called when memory allocation fails. Such a functionmust comply with the following requirements:

A redefined error function might, e.g., print a message and terminatethe program. The user-written error function becomes part of the allocationsystem through the functionset_new_handler.

Such an error function is illustrated below ( This implementationapplies to theGNUC/C++ requirements. Actually using the program givenin the next example is not advised, as it probably enormously slows down yourcomputer due to the resulting use of the operating system'sswap area.):

    #include <iostream>    #include <string>    using namespace std;    void outOfMemory()    {        cout << "Memory exhausted. Program terminates." << '\n';        exit(1);    }    int main()    {        long allocated = 0;        set_new_handler(outOfMemory);       // install error function        while (true)                        // eat up all memory        {            new int [100000]();            allocated += 100000 * sizeof(int);            cout << "Allocated " << allocated << " bytes\n";        }    }
Once the new error function has been installed it is automatically invokedwhen memory allocation fails, and the program is terminated. Memoryallocation may fail in indirectly called code as well, e.g., when constructingor using streams or when strings are duplicated by low-level functions.

So far for the theory. On some systems the `out of memory' conditionmay actually never be reached, as the operating system may interfere beforetherun-time support system gets a chance to stop the program.

The traditional memory allocation functions (likestrdup,malloc,realloc etc.) do not trigger thenew handler when memory allocationfails and should be avoided inC++ programs.

9.3: The assignment operator

InC++ struct and class type objects can be directly assigned new valuesin the same way as this is possible inC. The defaultaction of such an assignment for non-class type data members is a straightbyte-by-byte copy from one data member to another. For now we'll use thefollowing simple classPerson:
    class Person    {        char *d_name;        char *d_address;        char *d_phone;        public:            Person();            Person(char const *name, char const *addr, char const *phone);            ~Person();        private:            char *strdupnew(char const *src);   // returns a copy of src.    };    // strdupnew is easily implemented, here is its inline implementation:    inline char *Person::strdupnew(char const *src)    {        return strcpy(new char [strlen(src) + 1], src);    }

Person's data members are initialized to zeroes or to copies of theNTBSs passed toPerson's constructor, using some variant ofstrdup. The allocated memory is eventually returned byPerson'sdestructor.

Now consider the consequences of usingPerson objects in the followingexample:

    void tmpPerson(Person const &person)    {        Person tmp;        tmp = person;    }

Here's what happens whentmpPerson is called:

Now a potentially dangerous situation has been created. The actual valuesinperson arepointers, pointing to allocated memory. After theassignment this memory is addressed by two objects:personandtmp. This problematic assignment is illustrated in Figure5.

Figure 5: Private data and public interface functions of the class Person, using byte-by-byte assignment

Having executedtmpPerson, the object referenced byperson nowcontains pointers to deleted memory.

This is undoubtedly not a desired effect of using a function liketmpPerson. The deleted memory is likely to be reused by subsequentallocations. The pointer members ofperson have effectively becomewild pointers, as they don't point to allocatedmemory anymore. In general it can be concluded that

every class containing pointer data members is a potential candidate for trouble.
Fortunately, it is possible to prevent these troubles, as discussed next.

9.3.1: Overloading the assignment operator

Obviously, the right way to assign onePerson object to another, isnot to copy the content of the object bytewise. A better way is tomake an equivalent object. One having its own allocated memory containingcopies of the original strings.

The way to assign aPerson object to another isillustrated in Figure6.

Figure 6: Private data and public interface functions of the class Person, using the `correct' assignment.

There are several ways to assign aPerson object to another. One waywould be to define a special member function to handle the assignment. Thepurpose of this member function would be to create a copy of an object havingits ownname,address andphone strings. Such a member functioncould be:
    void Person::assign(Person const &other)    {            // delete our own previously used memory        delete[] d_name;        delete[] d_address;        delete[] d_phone;            // copy the other Person's data        d_name    = strdupnew(other.d_name);        d_address = strdupnew(other.d_address);        d_phone   = strdupnew(other.d_phone);    }

Usingassign we could rewrite the offending functiontmpPerson:

    void tmpPerson(Person const &person)    {        Person tmp;            // tmp (having its own memory) holds a copy of person        tmp.assign(person);            // now it doesn't matter that tmp is destroyed..    }

This solution is valid, although it only tackles a symptom. Itrequires the programmer to use a specific member function instead of theassignment operator. The original problem (assignment produces wild pointers)is still not solved. Since it is hard to `strictly adhere to a rule' a way tosolve the original problem is of course preferred.

Fortunately a solution exists usingoperator overloading: thepossibilityC++ offers to redefine the actions of an operator in a givencontext. Operator overloading was briefly mentioned earlier, when theoperators << and >> were redefined to be used with streams (likecin,cout andcerr), see section3.1.4.

Overloading the assignment operator is probably the most common form ofoperator overloading inC++. A word of warning is appropriate, though.The fact thatC++ allowsoperator overloading does not mean that thisfeature should indiscriminately be used. Here's what you should keep in mind:

An operator should simply do what it is designed to do. The phrase that'soften encountered in the context of operator overloading isdo as theints do. The way operators behave when applied toints is what isexpected, all other implementations probably cause surprises and confusion.Therefore, overloading the insertion (<<) and extraction (>>)operators in the context of streams is probably ill-chosen: the streamoperations have nothing in common with bitwise shift operations.

9.3.1.1: The member 'operator=()'

To add operator overloading to a class, the class interface is simplyprovided with a (usuallypublic) member function naming the particularoperator. That member function is thereupon implemented.

To overload the assignment operator=, a memberoperator=(Class const&rhs) is added to the class interface. Note that the function name consistsof two parts: the keywordoperator, followed by the operator itself. Whenwe augment a class interface with a member functionoperator=, then thatoperator isredefined for the class, which prevents the default operatorfrom being used. In theprevious section the functionassign was provided to solve the problems resulting from using thedefault assignment operator. Rather than using an ordinary memberfunctionC++ commonly uses a dedicated operator generalizing theoperator's default behavior to the class in which it is defined.

Theassign member mentioned before may be redefined as follows (the memberoperator= presented below is a first, rather unsophisticated, version ofthe overloaded assignment operator. It will shortly be improved):

    class Person    {        public:                             // extension of the class Person                                            // earlier members are assumed.            void operator=(Person const &other);    };

Its implementation could be

    void Person::operator=(Person const &other)    {        delete[] d_name;                      // delete old data        delete[] d_address;        delete[] d_phone;        d_name = strdupnew(other.d_name);   // duplicate other's data        d_address = strdupnew(other.d_address);        d_phone = strdupnew(other.d_phone);    }

This member's actions are similar to those of the previously mentionedmemberassign, but this member is automatically called when the assignmentoperator= is used. Actually there aretwo ways tocall overloaded operators as shown in the next example:

    void tmpPerson(Person const &person)    {        Person tmp;        tmp = person;        tmp.operator=(person);  // the same thing    }

Overloaded operators are seldom called explicitly, but explicit calls mustbe used (rather than using the plain operator syntax) when you explicitlywant to call the overloaded operator from a pointer to an object (it isalso possible to dereference the pointer first and then use the plain operatorsyntax, see the next example):

    void tmpPerson(Person const &person)    {        Person *tmp = new Person;        tmp->operator=(person);        *tmp = person;          // yes, also possible...        delete tmp;    }

9.4: The `this' pointer

A member function of a given class is always called in combination with anobject of its class. There is always an implicit `substrate' for the functionto act on.C++ defines a keyword,this, to reach this substrate.

Thethis keyword is a pointer variable that always contains theaddress of the object for which the memberfunction was called. Thethis pointer is implicitly declared by eachmember function (whetherpublic, protected, orprivate). Thethispointer is a constant pointer to an object of the member function'sclass. For example, the members of the classPerson implicitly declare:

    extern Person *const this;

A member function likePerson::name could be implemented in two ways:with or without using thethis pointer:

    char const *Person::name() const    // implicitly using `this'    {        return d_name;    }    char const *Person::name() const    // explicitly using `this'    {        return this->d_name;    }

Thethis pointer is seldom explicitly used, but situations do existwhere thethis pointer is actually required (cf. chapter16).

9.4.1: Sequential assignments and this

C++'s syntax allows for sequentialassignments, with the assignment operator associating from right to left. Instatements like:
    a = b = c;

the expressionb = c is evaluated first, and its result in turn isassigned toa.

The implementation of the overloaded assignment operator we've encounteredthus far does not permit such constructions, as it returnsvoid.

This imperfection can easily be remedied using thethis pointer. Theoverloaded assignment operator expects a reference to an object of itsclass. It can alsoreturn a reference to an object of its class. Thisreference can then be used as an argument in sequential assignments.

The overloaded assignment operator commonly returns a reference to thecurrent object (i.e.,*this). The next version of the overloadedassignment operator for the classPerson thus becomes:

    Person &Person::operator=(Person const &other)    {        delete[] d_address;        delete[] d_name;        delete[] d_phone;        d_address = strdupnew(other.d_address);        d_name = strdupnew(other.d_name);        d_phone = strdupnew(other.d_phone);        // return current object as a reference        return *this;    }

Overloaded operators may themselves be overloaded. Consider thestringclass, having overloaded assignment operatorsoperator=(std::string const&rhs), operator=(char const *rhs), and several more overloadedversions. These additional overloaded versions are there to handle differentsituations which are, as usual, recognized by their argument types. Theseoverloaded versions all follow the same mold: when necessary dynamicallyallocated memory controlled by the object is deleted; new values are assignedusing the overloaded operator's parameter values and*this is returned.

9.5: The copy constructor: initialization vs. assignment

Consider the classStrings, introduced in section9.2,once again. As it contains several primitive type data members as well as apointer to dynamically allocated memory it needs a constructor, a destructor,and an overloaded assignment operator. In fact the class offers twoconstructors: in addition to the default constructor it offers a constructorexpecting achar const *const * and asize_t.

Now consider the following code fragment. The statement references arediscussed following the example:

    int main(int argc, char **argv)    {        Strings s1(argv, argc);     // (1)        Strings s2;                 // (2)        Strings s3(s1);             // (3)        s2 = s1;                        // (4)    }

In the above example three objects were defined, each using a differentconstructor. The actually used constructor was deduced from the constructor'sargument list.

The copy constructor encountered here is new. It does not result in acompilation error even though it hasn't been declared in the classinterface. This takes us to the following rule:

A copy constructor is (almost) always available, even if it isn't declared in the class's interface.
The reason for the `(almost)' is given in section9.7.1.

The copy constructor made available by the compiler is also called thetrivial copy constructor. Its use can easily be suppressed (using the=delete idiom). The trivial copy constructor performs a byte-wise copyoperation of the existing object's primitive data to the newly created object,calls copy constructors to intialize the object's class data members fromtheir counterparts in the existing object and, when inheritance is used, callsthe copy constructors of the base class(es) to initialize the new object'sbase classes.

Consequently, in the above example the trivial copy constructor isused. As it performs a byte-by-byte copy operation of the object'sprimitive type data members that is exactly what happens at statement 3.By the times3 ceases to exist its destructor deletes its array ofstrings. Unfortunatelyd_string is of a primitive data type and so it alsodeletess1's data. Once again we encounter wild pointers as a result of anobject going out of scope.

The remedy is easy: instead of using the trivial copy constructor a copyconstructor must explicitly be added to the class's interface and itsdefinition must prevent the wild pointers, comparably to the way this wasrealized in the overloaded assignment operator. An object's dynamicallyallocated memory isduplicated, so that it contains its own allocateddata. But note that if a class also reserves extra (raw) memory, i.e., if itsupports extra memory capacity, then that unused extra capacity is not madeavailable in the copy-constructed object.

Copy constructioncan be used to shed excesscapacity. Copy constructors do nothave to shed excess capacity. E.g.,when copy-constructingstd::string objects the destination object definesthe same capacity as the source object. The copy constructor is simpler thanthe overloaded assignment operator in that it doesn't have to deletepreviously allocated memory. Since the object is going to be created no memoryhas already been allocated.

Strings's copy constructor can be implemented as follows:

    Strings::Strings(Strings const &other)    :        d_string(new string[other.d_size]),        d_size(other.d_size)    {        for (size_t idx = 0; idx != d_size; ++idx)            d_string[idx] = other.d_string[idx];    }

The copy constructor is always called when an object is initialized usinganother object of its class. Apart from the plain copy construction that weencountered thus far, here are other situations where the copy constructor isused:

Herestore is used to initializecopy's return value. The returnedStrings object is a temporary, anonymous object that may beimmediately used by code callingcopy but no assumptions can be made aboutits lifetime thereafter.

9.6: Revising the assignment operator

The overloaded assignment operator has characteristics also encountered withthe copy constructor and the destructor: The copy constructor and the destructor clearly are required. If theoverloaded assignment operator also needs to return allocated memory and toassign new values to its data members couldn't the destructor and copyconstructor be used for that?

As we've seen in our discussion of the destructor (section9.2) the destructor can explicitly be called, but that doesn'thold true for the (copy) constructor. But let's briefly summarize what anoverloaded assignment operator is supposed to do:

The second part surely looks a lot like copy construction. Copyconstruction becomes even more attractive after realizing that the copyconstructor also initializes any reference data members the class mighthave. Realizing the copy construction part is easy: just define a local objectand initialize it using the assignment operator's const reference parameter,like this:
    Strings &operator=(Strings const &other)    {        Strings tmp(other);        // more to follow        return *this;    }

You may think the optimizationoperator=(Strings tmp) is attractive,but let's postpone that for a little while (at least until section9.7).

Now that we've done the copying part, what about the deleting part? Andisn't there another slight problem as well? After all we copied all right, butnot into our intended (current,*this) object.

At this point it's time to introduceswapping. Swapping two variablesmeans that the two variables exchange their values. We'll discuss swapping indetail in the next section, but let's for now assume that we've added a memberswap(Strings &other) to our classStrings. This allows us tocompleteString'soperator= implementation:

    Strings &operator=(Strings const &other)    {        Strings tmp(other);        swap(tmp);        return *this;    }

This implementation ofoperator= is generic: it can be applied toevery class whose objects are swappable. How does it work?

Nice?

9.6.1: Swapping

Many classes (e.g.,std::string) offerswap members allowing us toswap two of their objects. TheStandard Template Library (STL, cf. chapter18) offers various functions related to swapping. There is even aswapgeneric algorithm (cf. section19.1.55), which is commonlyimplemented using the assignment operator. When implementing aswap memberfor our classStrings it could be used, provided that all ofString'sdata members can be swapped. As this is true (why this is true isdiscussed shortly) we can augmentclass Strings with aswap member:
    void Strings::swap(Strings &other)    {        std::swap(d_string, other.d_string);        std::swap(d_size, other.d_size);    }

Having added this member toStrings the copy-and-swap implementationofString::operator= can now be used.

When two variables (e.g.,double one anddouble two) are swapped,each one holds the other one's value after the swap. So, ifone == 12.50andtwo == -3.14 then afterswap(one, two) one == -3.14 andtwo ==12.50.

Variables of primitive data types (pointers and the built-in types) can beswapped, class-type objects can be swapped if their classes offer aswapmember.

So should we provide our classes with a swap member, and if so, how shouldit be implemented?

The above example (Strings::swap) shows the standard way to implementaswap member: each of its data members are swapped in turn. But there aresituations where a class cannot implement a swap member this way, even if theclass only defines data members of primitive data types. Consider thesituation depicted in figure7.

Figure 7: Swapping a linked list

In this figure there are four objects, each object has a pointer pointing tothe next object. The basic organization of such a class looks like this:

    class List    {        List *d_next;        ...    };

Initially four objects have theird_next pointer set to the nextobject: 1 to 2, 2 to 3, 3 to 4. This is shown in the upper half of thefigure. At the bottom half it is shown what happens if objects 2 and 3 areswapped: 3'sd_next point is now at object 2, which still points to 4; 2'sd_next pointer points to 3's address, but 2'sd_next is now at object3, which is therefore pointing to itself. Bad news!

Another situation where swapping of objects goes wrong happens with classeshaving data members pointing or referring to data members of the sameobject. Such a situation is shown in figure8.

Figure 8: Swapping objects with self-referential data

Here, objects have two data members, as in the following class setup:

    class SelfRef    {        size_t *d_ownPtr;       // initialized to &d_data        size_t d_data;    };

The top-half of figure8 shows two objects; their upper datamembers pointing to their lower data members. But if these objects are swappedthen the situation shown in the figure's bottom half is encountered. Here thevalues at addresses a and c are swapped, and so, rather than pointing to theirbottom data members they suddenly point to other object's data members. Again:bad news.

The common cause of these failing swapping operations is easily recognized:simple swapping operations must be avoided when data members point or refer todata that is involved in the swapping. If, in figure8 the a and cdata members would point to information outside of the two objects (e.g., ifthey would point to dynamically allocated memory) then the simple swappingwould succeed.

However, the difficulty encountered with swappingSelfRef objects does notimply that twoSelfRef objects cannot be swapped; it only means that wemust be careful when designingswap members. Here is an implementation ofSelfRef::swap:

    void SelfRef::swap(SelfRef &other)    {        swap(d_data, other.d_data);    }

In this implementation swapping leaves the self-referential data memberas-is, and merely swaps the remaining data. A similarswap member could bedesigned for the linked list shown in figure7.

9.6.1.1: Fast swapping

As we've seen with placementnew objects can be constructed in blocksof memory ofsizeof(Class) bytes large. And so, two objects of the sameclass each occupysizeof(Class) bytes.

If objects of our class can be swapped, and if our class's data members donot refer to data actually involved in the swapping operation then a very fastswapping method that is based on the fact that we know how large our objectsare can be implemented.

In this fast-swap method we merely swap the content of thesizeof(Class)bytes. This procedure may be applied to classes whose objects may be swappedusing a member-by-member swapping operation and can (in practice, althoughthis probably overstretches the allowed operations as described by theC++ ANSI/ISO standard) also be used in classes having reference datamembers. It simply defines a buffer ofsizeof(Class) bytes and performs acircularmemcpy operation. Here is its implementation for a hypotheticalclassClass. It results in very fast swapping:

    #include <cstring>    void Class::swap(Class &other)    {        char buffer[sizeof(Class)];        memcpy(buffer, &other, sizeof(Class));        memcpy(static_cast<void *>(&other), this,   sizeof(Class));        memcpy(static_cast<void *>(this), buffer, sizeof(Class));    }

Thestatic_cast formemcpy's destination address is used to prevent acompiler complaint: sinceClass is a class-type, the compiler (rightly)warns against bluntly copying bytes. But usingmemcpy is fine if you'reClass's developer and know what you're doing.

Here is a simple example of a class defining a reference data member andoffering aswap member implemented like the one above. The reference datamembers are initialized to external streams. After running the programonecontains twohello to 1 lines,two contains twohello to 2 lines(for brevity all members ofReference are defined inline):

    #include <fstream>    #include <cstring>    class Reference    {        std::ostream &d_out;        public:            Reference(std::ostream &out)            :                d_out(out)            {}            void swap(Reference &other)            {                char buffer[sizeof(Reference)];                memcpy(buffer, this, sizeof(Reference));                memcpy(static_cast<void *>(this), &other, sizeof(Reference));                memcpy(static_cast<void *>(&other), buffer,                                                    sizeof(Reference));            }            std::ostream &out()            {                return d_out;            }    };    int main()    {        std::ofstream one{ "one" };        std::ofstream two{ "two" };        Reference ref1{ one };          // ref1/ref2 hold references to        Reference ref2{ two };          // the streams        ref1.out() << "hello to 1\n";   // generate some output        ref2.out() << "hello to 2\n";        ref1.swap(ref2);        ref2.out() << "hello to 1\n";   // more output        ref1.out() << "hello to 2\n";    }

Fast swapping should only be used for self-defined classes for which it can beproven that fast-swapping does not corrupt its objects, when swapped.

9.7: Moving data

Traditionally,C++ offered two ways to assign the information pointed toby a data member of a temporary object to anlvalue object. Either a copyconstructor or reference counting had to be used. In addition to these twomethodsC++ now also supportsmove semantics, allowingtransferof the data pointed to by a temporary object to its destination.

Moving information is based on the concept of anonymous (temporary)data. Temporary values are returned by functions likeoperator-() andoperator+(Type const &lhs, Type const &rhs), and in general by functionsreturning their results `by value' instead of returning references orpointers.

Anonymous values are always short-lived. When the returned values areprimitive types (int, double, etc.) nothing special happens, but if aclass-type object is returned by value then its destructor can be calledimmediately following the function call that produced the value. In any case,the value itself becomes inaccessible immediately after the call. Of course, atemporary return value may be bound to a reference (lvalue or rvalue), but asfar as the compiler is concerned the value now has a name, which by itselfends its status as a temporary value.

In this section we concentrate on anonymous temporary values and show how theycan be used to improve the efficiency of object construction and assignment.These special construction and assignment methods are known asmoveconstruction andmove assignment. Classes supporting move operations arecalledmove-aware.

Classes allocating their own memory usually benefit from becomingmove-aware. But a class does not have to use dynamic memory allocation beforeit can benefit from move operations. Most classes using composition (orinheritance where the base class uses composition) can benefit from moveoperations as well.

Movable parameters for classClass take the formClass &&tmp. Theparameter is anrvalue reference, and a rvalue reference only binds to ananonymous temporary value. The compiler is required to call functions offeringmovable parameters whenever possible. This happens when the class definesfunctions supportingClass && parameters and an anonymous temporary valueis passed to such functions. Once a temporary value has a name (which alreadyhappens inside functions definingClass const & orClass &&tmpparameters as within such functions the names of these parameters areavailable) it is no longer ananonymous temporary value, and within suchfunctions the compiler no longer calls functions expecting anonymoustemporary values when the parameters are used as arguments.

The next example (using inline member implementations for brevity) illustrateswhat happens if a non-const object, a temporary object and a const object arepassed to functionsfun for which these kinds of parameters weredefined. Each of these functions call a functiongun for which these kindsof parameters were also defined. The first timefun is called it (asexpected) callsgun(Class &). Thenfun(Class &&) is called as itsargument is an anonymous (temporary) object. However, insidefun theanonymous value has received a name, and so it isn't anonymousanymore. Consequently,gun(Class &) is called once again. Finallyfun(Class const &) is called, and (as expected)gun(Class const &) isnow called.

#include <iostream>using namespace std;class Class{    public:    Class()    {};    void fun(Class const &other)    {        cout << "fun: Class const &\n";        gun(other);    }    void fun(Class &other)    {        cout << "fun: Class &\n";        gun(other);    }    void fun(Class &&tmp)    {        cout << "fun: Class &&\n";        gun(tmp);    }    void gun(Class const &other)    {        cout << "gun: Class const &\n";    }    void gun(Class &other)    {        cout << "gun: Class &\n";    }    void gun(Class &&tmp)    {        cout << "gun: Class &&\n";    }};int main(){    Class c1;    c1.fun(c1);    c1.fun(Class());    Class const c0;    c1.fun(c0);}

Generally it is pointless to define a function having anrvalue reference return type. The compiler decides whether or not to usean overloaded member expecting an rvalue reference on the basis of theprovided argument. If it is an anonymous temporary it calls the functiondefining the rvalue reference parameter, if such a function is available. Anrvalue reference return type is used, e.g., with thestd::move call, tokeep the rvalue reference nature of its argument, which is known to be atemporary anonymous object. Such a situation can be exploited also in asituation where a temporary object is passed to (and returned from) a functionwhich must be able to modify the temporary object. The alternative, passing aconst &, is less attractive as it requires aconst_cast before theobject can be modified. Here is an example:

    std::string &&doubleString(std::string &&tmp)    {        tmp += tmp;        return std::move(tmp);    }

This allows us to do something like

    std::cout << doubleString("hello "s);

to inserthello hello intocout.

The compiler, when selecting a function to call applies a fairly simplealgorithm, and also considers copy elision. This is covered shortly (section9.8).

9.7.1: The move constructor (dynamic data)

Our classStrings has, among other members, a data memberstring*d_string. Clearly,Strings should define a copy constructor, adestructor and an overloaded assignment operator.

Now consider the following functionloadStrings(std::istream &in)extracting the strings for aStrings object fromin. Next, theStrings object filled byloadStrings is returned by value. ThefunctionloadStrings returns a temporary object, which can then used toinitialize an externalStrings object:

    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 aStrings object are required:

We can improve the above procedure by defining amove constructor. Here is the declaration of theStrings class moveconstructor:
    Strings(Strings &&tmp);

Move constructors of classes using dynamic memory allocation are allowedto assign the values of pointer data members to their own pointer data memberswithout requiring them to make copies of the source's data. Next, thetemporary's pointer value is set to zero to prevent its destructor fromdestroying data now owned by the just constructed object. The move constructorhasgrabbed orstolen the data from the temporary object. This isOK as the temporary object cannot be referred to again (as it is anonymous, itcannot be accessed by other code) and we may assume that the temporary objectscease to exist shortly after the move-constructor's call. Here is animplementation ofStrings move constructor:

    Strings::Strings(Strings &&tmp)    :        d_string(tmp.d_string),        d_size(tmp.d_size),        d_capacity(tmp.d_capacity)    {        tmp.d_string = 0;        tmp.d_capacity = 0;        tmp.d_size = 0;    }
Move construction (in general: moving) must leave the object from whichinformation was moved in a valid state. It is not specified in what way thatvalid state must be realized, but a goodrule of thumb is to return theobject to its default constructed state. In the above illustration of a moveconstructortmp.d_size, tmp.d_capacity andtmp.d_string are all set to0. It's up to the author of theStrings class to decide whether or nottheString's members can all be set to 0. If there's a default constructordoing exactly that then assigning zeroes is fine. Ifd_capacity is doubledonced_size == d_capacity then settingd_capactiy to 1 and lettingd_string point to a newly allocated block of raw memory the size of astring might be attractive. The source object's capacity might evenremain as-is. E.g., when movingstd::string objects the source object'scapacity isn't altered.

In section9.5 it was stated that the copy constructor is almostalways available.Almost always as the declaration of a move constructorsuppresses the default availability of the copy constructor. The default copyconstructor is also suppressed if amove assignment operator is declared(cf. section9.7.3).

The following example shows a simple classClass, declaring a moveconstructor. In themain function following the class interface aClass object is defined which is then passed to the constructor of asecondClass object. Compilation fails with the compiler reporting:

    error: cannot bind 'Class' lvalue to 'Class&&'    error:   initializing argument 1 of 'Class::Class(Class&&)'

class Class{    public:        Class() = default;        Class(Class &&tmp)        {}};int main(){    Class one;    Class two{ one };}

The cure is easy: after declaring a (possiblydefault) copyconstructor the error disappears:

class Class{    public:        Class() = default;        Class(Class const &other) = default;        Class(Class &&tmp)        {}};int main(){    Class one;    Class two{ one };}

9.7.2: The move constructor (composition)

Classes not using pointer members pointing to memory controlled by itsobjects (and not having base classes doing so, see chapter13)may also benefit from overloaded members expecting rvalue references. Theclass benefits from move operations when one or more of the composed datamembers themselves support move operations.

Move operations cannot be implemented if the class type of a composed datamember does not support moving or copying. Currently,stream classes fallinto this category.

An example of a move-aware class is the classstd:string. A classPerson could use composition by definingstd::string d_name andstd::string d_address. Its move constructor would then have the followingprototype:

    Person(Person &&tmp);

However, the following implementation of this move constructor isincorrect:

    Person::Person(Person &&tmp)    :        d_name(tmp.d_name),        d_address(tmp.d_address)    {}

It is incorrect asstring's copy constructors rather thanstring's move constructors are called. If you're wondering why thishappens then remember that move operations areonly performed foranonymous objects. To the compiler anything having a name isn't anonymous. Andso, by implication, having available a rvalue reference doesnot mean thatwe're referring to an anonymous object. But weknow that the moveconstructor is only called for anonymous arguments. To use the correspondingstring move operations we have to inform the compiler that we're talkingabout anonymous data members as well. For this a cast could be used (e.g.,static_cast<Person &&>(tmp)), but the C++-0x standard provides thefunctionstd::move toanonymize a named object. The correctimplementation ofPerson's move construction is, therefore:

    Person::Person(Person &&tmp)    :        d_name( std::move(tmp.d_name) ),        d_address( std::move(tmp.d_address) )    {}

The functionstd::move is (indirectly) declared by many headerfiles. If no header is already declaringstd::move then includeutility.

When a class using composition not only contains class type data membersbut also other types of data (pointers, references, primitive data types),then these other data types can be initialized as usual. Primitive data typemembers can simply be copied; references can be initialized as usual andpointers may use move operations as discussed in the previous section.

The compiler never calls move operations for variables having names. Let'sconsider the implications of this by looking at the next example, assumingthe classClass offers a move constructor and a copy constructor:

    Class factory();    void fun(Class const &other);   // a    void fun(Class &&tmp);          // b    void callee(Class &&tmp)    {        fun(tmp);                   // 1    }    int main()    {        callee(factory());    }

Realizing thatfun(tmp) might be called twice the compiler's choice isunderstandable. Iftmp's data would have been grabbed at the first call,the second call would receivetmp without any data. But at the last callwe might know thattmp is never used again and so we might like to ensurethatfun(Class &&) is called. For this, once again,std::move is used:
    fun(std::move(tmp));            // last call!

9.7.3: Move-assignment

In addition to the overloaded assignment operator amove assignmentoperator may be implemented for classes supporting move operations. In thiscase, if the class supports swapping the implementation is surprisinglysimple. No copy construction is required and the move assignment operator cansimply be implemented like this:
    Class &operator=(Class &&tmp)    {        swap(tmp);        return *this;    }

If swapping is not supported then the assignment can be performed for eachof the data members in turn, usingstd::move as shown in the previoussection with a classPerson. Here is an example showing how to do thiswith that classPerson:

    Person &operator=(Person &&tmp)    {        d_name = std::move(tmp.d_name);        d_address = std::move(tmp.d_address);        return *this;    }

As noted previously (section9.7.1) declaring a move assignmentoperator suppresses the default availability of the copy constructor. It ismade available again by declaring the copy constructor in the class'sinterface (and of course by providing an explicit implementation or by usingthe= default default implementation).

9.7.4: Revising the assignment operator (part II)

Now that we've familiarized ourselves with the overloaded assignment operatorand the move-assignment, let's once again have a look at theirimplementations for a classClass, supporting swapping through itsswap member. Here is the generic implementation of the overloadedassignment operator:
    Class &operator=(Class const &other)    {        Class tmp{ other };        swap(tmp);        return *this;    }

and this is the move-assignment operator:

    Class &operator=(Class &&tmp)    {        swap(tmp);        return *this;    }

They look remarkably similar in the sense that the overloaded assignmentoperator's code is identical to the move-assignment operator's code once acopy of theother object is available. Since the overloaded assignmentoperator'stmp object really is nothing but a temporaryClass objectwe can use this fact by implementing the overloaded assignment operator interms of the move-assignment. Here is a second revision of the overloadedassignment operator:

    Class &operator=(Class const &other)    {        Class tmp{ other };        return *this = std::move(tmp);    }

9.7.5: Moving and the destructor

Once a class becomes amove-aware class one should realize that itsdestructor still performs its job as implemented. Consequently, when movingpointer values from a temporary source to a destination the move constructorcommonly ensures that the temporary object's pointer value is set to zero, toprevent doubly freeing memory.

If a class defines pointers to pointer data members there usually is notonly a pointer that is moved, but also asize_t defining the number ofelements in the array of pointers.

Once again, consider the classStrings. Its destructor is implementedlike this:

    Strings::~Strings()    {        for (string **end = d_string + d_size; end-- != d_string; )            delete *end;        delete[] d_string;    }

The move constructor (and other move operations!) must realize that thedestructor not only deletesd_string, but also considersd_size. Whend_size andd_string are set to 0, the destructor (correctly) won'tdelete anything. In addition, when the class uses capacity-doubling onced_size ==d_capacity then the move constructor can still reset the source's(d_capacity) to 0, since it'sknown that thetmp object ceases toexist following the move-assignment:

    Strings::Strings(Strings &&tmp)    :        d_string(tmp.d_string),        d_size(tmp.d_size),        d_capacity(tmp.d_capacity)    {        tmp.d_string = 0;        tmp.d_capacity = 0;        tmp.d_size = 0;    }

Other variations are possible as well. The bottom line: the move construcormust ensure that after the destination object has grabbed the source object'sdata the source object remains in a valid state. That's easily accomplished byassigning the same values to its data members as set by the defaultconstructor.

9.7.6: Move-only classes

Classes may very well allow move semantics without offering copysemantics. Most stream classes belong to this category. Extending theirdefinition with move semantics greatly enhances their usability. Once movesemantics becomes available for such classes, so calledfactory functions (functions returning an object constructed by the function) can easily beimplemented. E.g.,
    // assume char *filename    ifstream inStream(openIstream(filename));

For this example to work anifstream constructor must offer a moveconstructor. This ensures that only one object refers to the openistream.

Once classes offer move semantics their objects can also safely be storedin standard containers (cf. chapter12). When such containersperform reallocations (e.g., when their sizes are enlarged) they use theobject's move constructors rather than their copy constructors. As move-onlyclasses suppress copy semantics containers storing objects of move-onlyclasses implement the correct behavior in that it is impossible to assign suchcontainers to each other.

9.7.7: Default move constructors and assignment operators

As we've seen, classes by default offer a copy constructor and assignmentoperator. These class members are implemented so as to provide basic support:data members of primitive data types are copied byte-by-byte, but for classtype data members their corresponding copy constructors c.q. assignmentoperators are called. The compiler also attempts to provide defaultimplementations for move constructors and move assignment operators. However,the default constructors and assignment operators cannot always be provided.

These are the rules the compiler applies when deciding what to provide or notto provide:

If default implementations of copy or move constructors or assignmentoperators are suppressed, but they should be available, then it's easy toprovide the default implementations by specifying the required signatures, towhich the specification `= default' is added.

Here is an example of a class offering all defaults: constructor, copyconstructor, move constructor, copy assignment operator and move assignmentoperator:

    class Defaults    {        int d_x;        Mov d_mov;    };

Assuming thatMov is a class offering move operations in additionto the standard copy operations, then the following actions are performedon the destination'sd_mov andd_x:

    Defaults factory();    int main()    {                              Mov operation:    d_x:                                   ---------------------------      Defaults one;                Mov(),            undefined      Defaults two(one);           Mov(Mov const &), one.d_x      Defaults three(factory());   Mov(Mov &&tmp),   tmp.d_x      one = two;                   Mov::operator=(   two.d_x                                        Mov const &),      one = factory();             Mov::operator=(   tmp.d_x                                        Mov &&tmp)    }

If,Defaults declares at least one constructor (not being the copy- ormove constructor) as well as the copy assignment operators then only thedefault copy- and declared assignment operator are available. E.g.:

    class Defaults    {        int d_x;        Mov d_mov;        public:            Defaults(int x);            Defaults &operator=(Default const &rhs);    };    Defaults factory();    int main()    {                              Mov operation:    resulting d_x:                                   --------------------------------      Defaults one;                ERROR: not available      Defaults two(one);           Mov(Mov const &),        one.d_x      Defaults three(factory());   Mov(Mov const &),        one.d_x      one = two;                   Mov::operatpr=(          two.d_x                                        Mov const &)      one = factory();             Mov::operator=(          tmp.d_x                                        Mov const &)    }

To reestablish the defaults, append= default to the appropriatedeclarations:

    class Defaults    {        int d_x;        Mov d_mov;        public:            Defaults()               = default;            Defaults(int x);            // Defaults(Default const &) remains available (by default)            Defaults(Defaults &&tmp) = default;            Defaults &operator=(Defaults const &rhs);            Defaults &operator=(Defaults &&tmp) = default;    };

Be cautious declaring defaults, as default implementations copy data membersof primitive types byte-by-byte from the source object to the destinationobject. This is likely to cause problems with pointer type data members.

The= default suffix can only be used when declaring constructors orassignment operators in the class's public section.

9.7.8: Moving: implications for class design

Here are some general rules to apply when designing classes offering valuesemantics (i.e., classes whose objects can be used to initialize otherobjects of their class and that can be asssigned to other objects of theirclass):

In the previous sections we've also encountered an important design principle that can be applied to move-aware classes:

Whenever a member of a class receives aconst & to an object of itsown class and creates a copy of that object to perform its actual actions on,then that function's implementation can be implemented by an overloadedfunction expecting an rvalue reference.
The former function can now call the latter by passingstd::move(tmp)to it. The advantages of this design principle should be clear: there is onlyone implementation of the actual actions, and the class automatically becomesmove-aware with respect to the involved function.

We've seen an initial example of the use of this principle in section9.7.4. Of course, the principle cannot be applied to the copyconstructor itself, as you need a copy constructor to make a copy. The copy-and move constructors must always be implemented independently from eachother.

9.8: Copy Elision and Return Value Optimization

When the compiler selects a member function (or constructor) it applies asimple set of rules, matching arguments with parameter types.

Below two tables are shown. The first table should be used in cases where afunction argument has a name, the second table should be used in cases wherethe argument is anonymous. In each table select the const or non-const columnand then use the topmost overloaded function that is available having thespecified parameter type.

The tables do not handle functions defining value parameters. If a functionhas overloads expecting, respectively, a value parameter and some form ofreference parameter the compiler reports an ambiguity when such a function iscalled. In the following selection procedure we may assume, without loss ofgenerality, that this ambiguity does not occur and that all parameter typesare reference parameters.

Parameter types matching a function's argument of typeT if the argumentis:

The tables show that eventuallyall arguments can be used with afunction specifying aT const & parameter. Foranonymous arguments asimilarcatch all is available having a higher priority:T const &&matches all anonymous arguments. Functions having this signature are normallynot defined as their implementations are (should be) identical to theimplementations of the functions expecting aT const & parameter. Sincethe temporary can apparently not be modified a function defining aT const&& parameter has no alternative but to copy the temporary's resources. Asthis task is already performed by functions expecting aT const &, thereis no need for implementing functions expectingT const && parameters, andit's considered bad style if you do.

As we've seen the move constructor grabs the information from a temporaryfor its own use. That is OK as the temporary is going to be destroyed afterthat anyway. It also means that the temporary's data members aremodified.

Having defined appropriate copy and/or move constructors it may besomewhat surprising to learn that the compiler may decide to stay clear of acopy or move operation. After all makingno copy andnot moving ismore efficient than copying or moving.

The option the compiler has to avoid making copies (or perform moveoperations) is calledcopy elision orreturn value optimization. Inall situations where copy or move constructions are appropriate the compilermay apply copy elision. Here are the rules. In sequence the compiler considersthe following options, stopping once an option can be selected:

All modern compilers apply copy elision. Here are some examples where it maybe encountered:
    class Elide;    Elide fun()         // 1    {        Elide ret;        return ret;    }    void gun(Elide par);    Elide elide(fun()); // 2    gun(fun());         // 3

9.9: Unrestricted Unions

Standard (C-type) unions can only have fields of basic types, likeint,double and pointers.C++ extends theC-type union concept by offeringunrestricted unions.

Unrestricted unions also allow data fields of types for which non-trivial copyconstructors were defined. Such data fields commonly are of class-types. Hereis an example of such an unrestricted union:

    union Union    {        int u_int;        std::string u_string;    };

One of its fields is defined as astd::string (having a constructor),turning this union into anunrestricted union. As an unrestricted uniondefines at least one field of a type having a constructor the question becomeshow these unions can be constructed and destroyed.

The destructor of a union consisting of, e.g. astd::string and anint should of course not call thestring's destructor if theunion's last (or only) use referred to itsint field. Likewise, whenthestd::string field is used, and processing switches from thestd::string to theint field,std::string's destructorshould be called before any assignment to theint field takes place.

The compiler does not solve the issue for us, and in fact does not at allimplement default constructors or destructors for unrestricted unions. If wetry to define an unrestricted union like the one shown above, an error messageis issued. E.g.,

    error: use of deleted function 'Union::Union()'    error: 'Union::Union()' is implicitly deleted because the default            definition would be ill-formed:    error: union member 'Union::u_string' with non-trivial            'std::basic_string<...>::basic_string() ...'

9.9.1: Implementing the destructor

Although the compiler won't provide (default) implementations for constructorsand destructors of unrestricted unions,we can (andmust). The taskisn't difficult, but there are some caveats.

Consider our unrestricted union's destructor. It clearly should destroyu_string's data if that is its currently active field; but it should donothing ifu_int is its currently active field. But how does thedestructor know what field to destroy? It doesn't, as the unrestricted unioncontains no information about what field is currently active.

This problem is tackled by embedding the unrestricted union in a largeraggregate (like a class or a struct) where it becomes a regular datamember. We still consider the unrestricted union a data type by itself, butits use requires caution. The surrounding class is provided with ad_fielddata member keeping track of the currently active union-field. Thed_fieldvalue is an enumeration value which is defined by the union. The actual use ofthe unrestricted union is completely controlled by the aggregate, freeing theaggregate's users from any administration related to the unrestricted union.

Using this design we start out with an explicit and empty implementation ofthe destructor, as there's no way to tell the destructor itself what field todestroy:

    Data::Union::~Union()    {};

Nevertheless, unrestricted unions must properly destroy their class-typefields. Since an unrestricted union itself doesn't know what its active fieldis, it must be informed about that by its surrounding class. To simplify thegeneralization to other types a static array of pointers to functionsdestroying the current field's value is used. This array is defined in theunion's private section as

    static void (Union::*s_destroy[])();

and it is initialized as:

    void (Union::*Union::s_destroy[])() =     {        &Union::destroyText,        &Union::destroyValue    };

Primitive data types normally don't need any special attention when theygo out of scope, sodestroyValue can be defined as an empty function:

    void Union::destroyValue()    {}

On the other hand, the memberdestroyText must explicitly callu_text's destructor:

    void Union::destroyText()    {        u_text.std::string::~string();    }

Proper destruction can now be realized by a single functionvoiddestroy(Field field) which simply calls the appropriate function:

    void Union::destroy(Field type)    {        (this->*s_destroy[type])();    }

Since the unrestricted union is defined as a data member of a surroundingclass, the surrounding class's destructor is responsible for the properdestruction of its unrestricted union. As the surrounding class keeps track ofthe currently active unrestricted union's field its implementation is easy:

    Data::~Data()    {        d_union.destroy(d_field);    }

9.9.2: Embedding an unrestricted union in a surrounding class

The unrestricted union becomes a data member of the surrounding aggregate(e.g.,class Data). Theclass Data is provided with a data memberUnion::Field d_field andData's users might query the currently activefield from, e.g., an accessorfield:

    class Data    {        Union::Field d_field;        Union d_union;            public:            Data(int value = 0);            Data(Data const &other);            Data(Data &&tmp);            Data(std::string const &text);            ~Data();                // empty body            Union::Field field() const;            ...    };

Data's constructors receiveint orstring values. To pass thesevalues tod_union, we needUnion constructors for the various unionfields.

The unrestricted union itself starts out like this:

    union Union    {        enum Field        {            TEXT,            VALUE        };            private:            std::string u_text;            int u_value;            public:            Union(Union const &other) = delete;            ~Union();               // empty                Union(int value);            Union(std::string const &text);            Union(Union const &other, Field type);            Union(Union &&tmp, Field type);            ...    };

The last twoUnion constructors are comparable to the standard copy-and move constructors. With unrestricted unions, however, the existing union'sactual type needs to be specified so that the correct field is initialized.To simplify the generalization to other types we apply a procedure that iscomparable to the procedure we followed for destroying an understricted union:we define a static array of pointers to copy-functions. This array is declaredin the union's private section as

    static void (Union::*s_copy[])(Union const &other);

and it is defined as:

    void (Union::*Union::s_copy[])(Union const &other) =     {        &Union::copyText,        &Union::copyValue    };

ThecopyText andcopyValue private members are responsible forcopyingother's data fields. However, there is a little snag. Althoughbasic types can directly be assigned, class-type fields cannot. Destinationfields cannot be initialized using member initializers as the field toinitialize depends on theField type that's passed to theconstructor. Because of that the initialization must be performed inside theconstructors' bodies. At that point the data fields aremerely a series of uninitialized bytes, and so placement new is used tocopy-construct class-type fields. Here are the implementations of the copyfunctions:

    void Union::copyValue(Union const &other)    {        u_value = other.u_value;    }    void Union::copyText(Union const &other)    {        new(&u_text) string{ other.u_text };    }

When implementing the union's move constructor other considerations must betaken into account. Since we're free to do whatever we want with the moveconstructor'sUnion &&tmp object, we can simply grab its current field,and store aVALUE type of value intotmp. For that we use theUnion'sswap facility, the current object's field, anotherUnionobject, and the otherUnion's field type (swapping is discussed in thenext section). Of course, if there isn't any primitive typed field thisdoesn't work. In that case field-specific move functions must be used,comparable to the ones used when copy-constructing aUnion object.

Now we're ready for the constructors' implementations:

    Union::Union(std::string const &text)    :        u_text(text)    {}        Union::Union(int value)    :        u_value(value)    {}    Union::Union(Union &&tmp, Field type)    {        swap(VALUE, tmp, type);    }    Union::Union(Union const &other, Field type)    {        (this->*s_copy[type])(other);    }    Data::Data(Data const &other)    :        d_field(other.d_field),        d_union(other.d_union, d_field)    {}    Data::Data(int value)    :        d_field(Union::VALUE),        d_union(value)    {}        Data::Data(Data const &other)    :        d_field(other.d_field),        d_union(other.d_union, d_field)    {}

9.9.3: Swapping unrestricted unions

Unrestricted unions should define a non-throwingswap member. It needsthree arguments: the current object's field, another union object, and thatunion's field. The prototype of our unrestricted union'sswap member,therefore, is:
    void swap(Field current, Union &other, Field next);

To implement it similar considerations as encountered with the copyconstructor apply. An unrestricted union havingk fields must supportk* k different swap situations. Representing these in ak * k matrix wenote that the diagonal elements refer to swapping identical elements for whichno special considerations apply (assuming swapping of identical data types issupported). The lower-triangle elements are identical to their transposedupper-triangle elements, and so they can use those elements after revertingthe current and other union objects. All field-specific swap functions can beorganized in ak x k static matrix of pointers to swapping members. ForUnion the declaration of that matrix is

    static void (Union::*s_swap[][2])(Union &other);

and its definition is

    void (Union::*Union::s_swap[][2])(Union &other) =     {      {  &Union::swap2Text,     &Union::swapTextValue},      {  &Union::swapValueText, &Union::swap2Value},    };

The diagonal and lower-triangle elements arestraightforwardly implemented. E.g.,

    void Union::swap2Text(Union &other)    {        u_text.swap(other.u_text);    }    void Union::swapValueText(Union &other)    {        other.swapTextValue(*this);    }

but implementing the upper-triangle elements requires some thought. Toinstall a class-type field placement new must again be used. But this timewe're not copying but moving, as the current object is going to lose itscontent. Like swapping, moving should always succeed. Following the moveconstruction the other object has received the current object's data. As thecurrent object keeps its valid state after the move, it must also explicitlybe destroyed to properly end its lifetime. Here is the implementation ofswapTextValue:

    void Union::swapTextValue(Union &other)    {        int value = other.u_value;  // save the int value                                    // install string at other        new(&other.u_text) string{ std::move(u_text) };         u_text.~string();           // remove the old field's union            u_value = value;            // store current's new value    }

When an unrestricted union has multiple class-type fields then whenswapping move construction must be applied to both unrestricted unions. Thisrequires a temporary. Assume an unrestricted union supports fields of classesThis andThat then to swap unrestricted unions using, respectively theThis andThat fields we do as follows:

    void ThisThat::swapThisThat(ThisThat &other)    {        This tmp{ std::move(u_this) };  // save the current object        u_this.~This();                 // properly destroy it                                        // install the other object at                                        // this object        new(&u_that) That{ std::move(other.u_that) };        other.u_that.~That();           // properly destroy the other                                         // object                                        // install this object's original                                         // value at the other object        new(&other.u_this) This{ std::move(tmp) };    }                                   // tmp is automatically destroyed

Now that unrestricted unions can be swapped, theirswap member can beused byswap members of surrounding classes. E.g.,

    void Data::swap(Data &other)    {        d_union.swap(d_field, other.d_union, other.d_field);            Union::Field field = d_field;   // swap the fields        d_field = other.d_field;        other.d_field = field;    }

9.9.4: Assignment

There are two ways to assign aData object to another one: copy assignmentand move assignment. Their implementations are standard:
    Data &Data::operator=(Data const &other)    // copy-assignment    {        Data tmp{ other };        swap(tmp);        return *this;    }    Data &Data::operator=(Data &&tmp)           // move-assignment    {        swap(tmp);        return *this;    }

Sinceswap has already been defined the assignment operators need nofurther attention: they are implemented using their standard implementations.

When unrestricted unions are used outside of surrounding classes asituation may arise where two unrestricted unions are directly assigned toeach other. In that case the unions' active fields must somehow beavailable. Sinceoperator= can only be defined having one parameter,simply passing an unrestricted union as its rvalue would lack informationabout the lvalue's and rvalue's active fields. Instead two members aresuggested:copy, doing copy assignment andmove, doing moveassignment. Their implementations closely resemble those of the standardassignment operators:

    void Union::copy(Field type, Union const &other, Field next)    {        Union tmp{ other, next };   // create a copy        swap(type, tmp, next);      // swap *this and tmp        tmp.destroy(type);          // destroy tmp    }            void Union::move(Field type, Union &&tmp, Field next)    {        swap(type, tmp, next);    }

In the source distribution you'll find a directoryyo/memory/examples/unions. It contains a small demo-program in whichUnion andData are used.

9.10: Aggregate Data Types

C++ inherited the struct concept fromC and extended it with the classconcept. Structs are still used inC++, mainly to store and pass aroundaggregates of different data types. A commonly used term for these structs isaggregate (in some languages known asplain old data(pod)). Aggregates are commonly used inC++ programs to merely combinedata in dedicated (struct) types. E.g., when a function must return adouble, abool andstd::string these three different data typesmay be aggregated using astruct that merely exists to pass alongvalues. Data protection and functionality is hardly ever an issue. For suchcasesC andC++ usestructs. But as aC++struct is just aclass with special access rights some members (constructors, destructor,overloaded assignment operator) may implicitly be defined. Aggregatescapitalize on this concept by requiring that their definitions remain assimple as possible, showing the following characteristics:

Aggregates can also be arrays, in which case the array elements are theaggregate's elements. If an aggregate is astruct its direct base classesare its elements (if any), followed by thestruct's data members, in theirdeclaration order. Here is an example:

    struct Outer    {        struct Inner        {            int     d_int;            double  d_double;        };        std::string d_string;        Inner d_inner;    };    Outer out{ "hello", { 1, 12.5} };

Outer out's d_string is initialized withhello", itsd_innermember has two data members:d_int is initialized to 1,d_double to12.5.

(Designated) initializer lists can also be used (cf. section3.3.5). Also, oftenstructured binding declarations (cf. section3.3.7.1) can be used to avoid explicitly defining an aggregate datatype.

9.11: Conclusion

Four important extensions to classes were introduced in this chapter: thedestructor, the copy constructor, the move constructor andthe overloaded assignment operator. In addition the importance ofswapping, especially in combination with the overloaded assignmentoperator, was stressed.

Classes having pointer data members, pointing to dynamically allocated memorycontrolled by the objects of those classes, arepotential sources of memory leaks. The extensions introduced in thischapter implement the standard defense against such memory leaks.

Encapsulation (data hiding) allows us to ensure that the object's dataintegrity is maintained. The automatic activation of constructors anddestructors greatly enhance our capabilities to ensure the data integrity ofobjects doing dynamic memory allocation.

A simple conclusion is therefore that classes whose objects allocate memorycontrolled by themselves must at least implement adestructor, anoverloaded assignment operator and acopy constructor. Implementing amove constructor remainsoptional, but it allows us to usefactory functions with classesnotallowing copy construction and/or assignment.

In the end, assuming the availability of at least a copy or move constructor,the compiler might avoid them usingcopy elision. The compiler is free touse copy elision wherever possible; it is, however, never a requirement. Thecompiler may therefore always decide not to use copy elision. In allsituations where otherwise a copy or move constructor would have been used thecompiler may consider to use copy elision.




[8]ページ先頭

©2009-2025 Movatter.jp