- Each type, whether a built-in or class type, defines the meaning of a (possibly empty) set of operations on objects of that type
- Each type also defines what happens when objects of the type are created. Initialization of objects of class type is defined by constructors. Types also control what happens when objects of the type are copied, assigned, or destroyed. Classes control these actions through special member functions: the copy constructor, the assignment operator, and the destructor. This chapter covers these operations
- When we define a new type, we specifyexplicitly or implicitlywhat happens when objects of that type are copied, assigned, and destroyed. We do so by defining special members: the copy constructor, the assignment operator, and the destructor. If we do not explicitly define the copy constructor or the assignment operator, the compiler will (usually) define them for us
- The copy constructor is a special constructor that has a single parameter that is a (usually const) reference to the class type. The copy constructor is used explicitly when we define a new object and initialize it from an object of the same type. It is used implicitly when we pass or return objects of that type to or from functions
- The destructor is complementary to the constructors: It is applied automatically when an object goes out of scope or when a dynamically allocated object is deleted. The destructor is used to free resources acquired when the object was constructed or during the lifetime of the object. Regardless of whether a class defines its own destructor, the compiler automatically executes the destructors for the nonstatic data members of the class
- Like constructors, the assignment operator may be overloaded by specifying different types for the right-hand operand. The version whose right-hand operand is of the class type is special: If we do not write one, the compiler will synthesize one for us
- Collectively, the copy constructor, assignment operator, and destructor are referred to as copy control. The compiler automatically implements these operations, but the class may define its own versions
- Copy control is an essential part of defining any C++ class. Programmers new to C++ are often confused by having to define what happens when objects are copied, assigned, or destroyed. This confusion is compounded because if we do not explicitly define these operations, the compiler defines them for usalthough they might not behave as we intend
- Often the compiler-synthesized copy-control functions are finethey do exactly the work that needs to be done. But for some classes, relying on the default definitions leads to disaster. Frequently, the most difficult part of implementing the copy-control operations is recognizing when we need to override the default versions. One especially common case that requires the class to define its own the copy-control members is if the class has a pointer member
Section 13.1 The Copy Constructor
- The constructor that takes a single parameter that is a (usually const) reference to an object of the class type itself is called the copy constructor. Like the default constructor, the copy constructor can be implicitly invoked by the compiler. The copy constructor is used to
- Explicitly or implicitly initialize one object from another of the same type
- Copy an object to pass it as an argument to a function
- Copy an object to return it from a function
- Initialize the elements in a sequential container
- Initialize elements in an array from a list of element initializers
- Forms of Object Definition
- Recall that C++ supports two forms of initialization (Section 2.3.3, p. 48): direct and copy. Copy-initialization uses the = symbol, and direct-initialization places the initializer in parentheses
- The copy and direct forms of initialization, when applied to objects of class type, are subtly different. Direct-initialization directly invokes the constructor matched by the arguments. Copy-initialization always involves the copy constructor. Copy-initialization first uses the indicated constructor to create a temporary object (Section 7.3.2, p. 247). It then uses the copy constructor to copy that temporary into the one we are creating
- For objects of class type, copy-initialization can be used only when specifying a single argument or when we explicitly build a temporary object to copy
- The copy form of initialization is primarily supported for compatibility with C usage. When it can do so, the compiler is permitted (but not obligated) to skip the copy constructor and create the object directly
- Usually the difference between direct- or copy-initialization is at most a matter of low-level optimization. However, for types that do not support copying, or when using a constructor that is nonexplicit (Section 12.4.4, p. 462) the distinction can be essential
- Parameters and Return Values
- As we know, when a parameter is a nonreference type (Section 7.2.1, p. 230), the argument is copied. Similarly, a nonreference return value (Section 7.3.2, p. 247) is returned by copying the value in the return statement
- When the parameter or return type is a class type, the copy is done by the copy constructor
- Initializing Container Elements
- The copy constructor is used to initialize the elements in a sequential container
- As a general rule (Section 9.1.1, p. 307), unless you intend to use the default initial value of the container elements, it is more efficient to allocate an empty container and add elements as the values for those elements become known
- Constructors and Array Elements
- If we provide no element initializers for an array of class type, then the default constructor is used to initialize each element. However, if we provide explicit element initializers using the normal brace-enclosed array initialization list (Section 4.1.1, p. 111), then each element is initialized using copy-initialization. An element of the appropriate type is created from the specified value, and then the copy constructor is used to copy that value to the corresponding element
- 13.1.1. The Synthesized Copy Constructor
- If we do not otherwise define the copy constructor, the compiler synthesizes one for us. Unlike the synthesized default constructor (Section 12.4.3, p. 458), a copy constructor is synthesized even if we define other constructors. The behavior of the synthesized copy constructor is to memberwise initialize the new object as a copy of the original object
- By memberwise, we mean that taking each nonstatic member in turn, the compiler copies the member from the existing object into the one being created. With one exception, the type of each member determines what it means to copy it. The synthesized copy constructor directly copies the value of members of built-in type. Members of class type are copied by using the copy constructor for that class. The one exception concerns array members. Even though we ordinarily cannot copy an array, if a class has a member that is an array, then the synthesized copy constructor will copy the array. It does so by copying each element
- The simplest conceptual model of memberwise initialization is to think of the synthesized copy constructor as one in which each data member is initialized in the constructor initializer list
- 13.1.2. Defining Our Own Copy Constructor
- The copy constructor is the constructor that takes a single parameter that is a (usually const) reference to the class type
class Foo {
public:
Foo(); // default constructor
Foo(const Foo&); // copy constructor
// ...
};
- Usually the parameter is a const reference, although we can also define the copy constructor to take a nonconst reference. Because the constructor is used (implicitly) to pass and return objects to and from functions, it usually should not be made explicit (Section 12.4.4, p. 462). The copy constructor should copy the members from its argument into the object that is being constructed
- For many classes, the synthesized copy constructor does exactly the work that is needed. Classes that contain only members that are of class type or members that are of built-in (but not pointer type) often can be copied without explicitly defining the copy constructor
- However, some classes must take control of what happens when objects are copied. Such classes often have a data member that is a pointer or that represents another resource that is allocated in the constructor. Other classes have bookkeeping that must be done whenever a new object is created. In both these cases, the copy constructor must be defined
- Often the hardest part about defining a copy constructor is recognizing that a copy constructor is needed. Defining the constructor is usually pretty easy once the need for the constructor is recognized. The copy constructor itself is defined like any other constructor: It has the same name as the name of the class, it has no return value, it may (should) use a constructor initializer to initialize the members of the newly created object, and it may do any other necessary work inside a function body
- The copy constructor is the constructor that takes a single parameter that is a (usually const) reference to the class type
- 13.1.3. Preventing Copies
- Some classes need to prevent copies from being made at all
- To prevent copies, a class must explicitly declare its copy constructor as private
- If the copy constructor is private, then user code will not be allowed to copy objects of the class type. The compiler will reject any attempt to make a copy
- However, the friends and members of the class could still make copies. If we want to prevent copies even within the friends and members, we can do so by declaring a (private) copy constructor but not defining it
- Most Classes Should Define Copy and Default Constructors
- Classes that do not define the default constructor and/or the copy constructor impose serious limits on users of the class. Objects of classes that do not allow copies may be passed to (or returned from) a function only as a reference. They also may not be used as elements in a container
- It is usually best to defineeither implicitly or explicitlythe default and copy constructors. The default constructor is synthesized only if there are no other constructors. If the copy constructor is defined, then the default constructor must be defined as well
- Classes that do not define the default constructor and/or the copy constructor impose serious limits on users of the class. Objects of classes that do not allow copies may be passed to (or returned from) a function only as a reference. They also may not be used as elements in a container
- Some classes need to prevent copies from being made at all
Section 13.2 The Assignment Operator
- Just as classes control how objects are initialized, they also define what happens when objects of their type are assigned
- As with the copy constructor, the compiler synthesizes an assignment operator if the class does not define its own
- Introducing Overloaded Assignment
- Overloaded operators are functions that have the name operator followed by the symbol for the operator being defined. Hence, we define assignment by defining a function named operator=. Like any other function, an operator function has a return type and a parameter list. The parameter list must have the same number of parameters (including the implicit this parameter if the operator is a member) as the operator has operands. Assignment is binary, so the operator function has two parameters: The first parameter corresponds to the left-hand operand, and the second to the right-hand operand
- Most operators may be defined as member or nonmember functions. When an operator is a member function, its first operand is implicitly bound to the this pointer. Some operators, assignment among them, must be members of the class for which the operator is defined. Because assignment must be a member of its class, this is bound to a pointer to the left-hand operand. The assignment operator, therefore, takes a single parameter that is an object of the same class type. Usually, the right-hand operand is passed as a const reference
- The return type from the assignment operator should be the same as the return from assignment for the built-in types (Section 5.4.1, p. 160). Assignment to a built-in type returns a reference to its left-hand operand. Therefore, the assignment operator also returns a reference to the same type as its class
- Overloaded operators are functions that have the name operator followed by the symbol for the operator being defined. Hence, we define assignment by defining a function named operator=. Like any other function, an operator function has a return type and a parameter list. The parameter list must have the same number of parameters (including the implicit this parameter if the operator is a member) as the operator has operands. Assignment is binary, so the operator function has two parameters: The first parameter corresponds to the left-hand operand, and the second to the right-hand operand
- The Synthesized Assignment Operator
- The synthesized assignment operator operates similarly to the synthesized copy constructor. It performs memberwise assignment: Each member of the right-hand object is assigned to the corresponding member of the left-hand object. Except for arrays, each member is assigned in the usual way for its type. For arrays, each array element is assigned
- The synthesized assignment operator assigns each member in turn, using the built-in or class-defined assignment operator as appropriate to the type of the member. The operator returns *this, which is a reference to the left-hand object
- The synthesized assignment operator operates similarly to the synthesized copy constructor. It performs memberwise assignment: Each member of the right-hand object is assigned to the corresponding member of the left-hand object. Except for arrays, each member is assigned in the usual way for its type. For arrays, each array element is assigned
- Copy and Assign Usually Go Together
- Classes that can use the synthesized copy constructor usually can use the synthesized assignment operator as well
- However, a class may define its own assignment operator. In general, if a class needs a copy constructor, it will also need an assignment operator
- In fact, these operations should be thought of as a unit. If we require one, we almost surely require the other
- Classes that can use the synthesized copy constructor usually can use the synthesized assignment operator as well
Section 13.3 The Destructor
- The destructor is a special member function that can be used to do whatever resource deallocation is needed. It serves as the complement to the constructors of the class
- When a Destructor Is Called
- The destructor is called automatically whenever an object of its class is destroyed
- An object that is dynamically allocated is destroyed only when a pointer pointing to the object is delete d. If we do not delete a pointer to a dynamically allocated object, then the destructor is never run on that object. The object will persist forever, leading to a memory leak. Moreover, any resources used inside the object will also not be released
- The destructor is not run when a reference or a pointer to an object goes out of scope. The destructor is run only when a pointer to a dynamically allocated object is deleted or when an actual object (not a reference to the object) goes out of scope
- Destructors are also run on the elements of class type in a container whether a library container or built-in array when the container is destroyed
- The elements in the container are always destroyed in reverse order: The element indexed by size() - 1 is destroyed first, followed by the one indexed by size() - 2 and so on until element [0], which is destroyed last
- The destructor is called automatically whenever an object of its class is destroyed
- When to Write an Explicit Destructor
- Many classes do not require an explicit destructor. In particular, a class that has a constructor does not necessarily need to define its own destructor. Destructors are needed only if there is work for them to do. Ordinarily they are used to relinquish resources acquired in the constructor or during the lifetime of the object
- A useful rule of thumb is that if a class needs a destructor, it will also need the assignment operator and a copy constructor. This rule is often referred to as the Rule of Three, indicating that if you need a destructor, then you need all three copy-control members
- A destructor is not limited only to relinquishing resources. A destructor, in general, can perform any operation that the class designer wishes to have executed subsequent to the last use of an object of that class
- Many classes do not require an explicit destructor. In particular, a class that has a constructor does not necessarily need to define its own destructor. Destructors are needed only if there is work for them to do. Ordinarily they are used to relinquish resources acquired in the constructor or during the lifetime of the object
- The Synthesized Destructor
- Unlike the copy constructor or assignment operator, the compiler always synthesizes a destructor for us. The synthesized destructor destroys each nonstatic member in the reverse order from that in which the object was created. In consequence, it destroys the members in reverse order from which they are declared in the class. For each member that is of class type, the synthesized destructor invokes that member's destructor to destroy the object
- Destroying a member of built-in or compound type has no effect. In particular, the synthesized destructor does not delete the object pointed to by a pointer member
- Unlike the copy constructor or assignment operator, the compiler always synthesizes a destructor for us. The synthesized destructor destroys each nonstatic member in the reverse order from that in which the object was created. In consequence, it destroys the members in reverse order from which they are declared in the class. For each member that is of class type, the synthesized destructor invokes that member's destructor to destroy the object
- How to Write a Destructor
- Classes that do allocate resources usually need to define a destructor to free those resources. The destructor is a member function with the name of the class prefixed by a tilde (~). It has no return value and takes no parameters. Because it cannot specify any parameters, it cannot be overloaded. Although we can define multiple class constructors, we can provide only a single destructor to be applied to all objects of our class
- An important difference between the destructor and the copy constructor or assignment operator is that even if we write our own destructor, the synthesized destructor is still run
- Classes that do allocate resources usually need to define a destructor to free those resources. The destructor is a member function with the name of the class prefixed by a tilde (~). It has no return value and takes no parameters. Because it cannot specify any parameters, it cannot be overloaded. Although we can define multiple class constructors, we can provide only a single destructor to be applied to all objects of our class
Section 13.4 A Message-Handling Example
- The Message Class
- Copy Control for the Message Class
- When we write our own copy constructor, we must explicitly copy any members that we want copied. An explicitly defined copy constructor copies nothing automatically
- As with any other constructor, if we do not initialize a class member, then that member is initialized using the member's default constructor. Default initialization in a copy constructor does not use the member's copy constructor
- The put_Msg_in_Folders Member
- Message Assignment Operator
- The assignment operator starts by checking that the left- and right-hand operands are not the same. We do this check for reasons that will become apparent as we walk through the rest of the function
- Assignment involves obliterating the left-hand operand. Once the members of the left-hand operand are destroyed, those in the right-hand operand are assigned to the corresponding left-hand members. If the objects were the same, then destroying the left-hand members would also destroy the right-hand members
- It is crucially important for assignment operators to work correctly, even when an object is assigned to iself. A common way to ensure this behavior is by checking explicitly for self-assignment
- The remove_Msg_from_Folders Member
- The Message Destructor
- The assignment operator often does the same work as is needed in the copy constructor and destructor. In such cases, the common work should be put in private utility functions
Section 13.5 Managing Pointer Members
- Classes that contain pointers require careful attention to copy control. The reason they must do so is that copying a pointer copies only the address in the pointer. Copying a pointer does not copy the object to which the pointer points.
- When designing a class with a pointer member, the first decision a class author must make is what behavior that pointer should provide. When we copy one pointer to another, the two pointers point to the same object. When two pointers point to the same object, it is possible to use either pointer to change the underlying object. Similarly, it is possible for one pointer to delete the object even though the user of the other pointer still thinks the underlying object exists
- By default, a pointer member has the same behavior as a pointer object. However, through different copy-control strategies we can implement different behavior for pointer members
- Most C++ classes take one of three approaches to managing pointer members
- The pointer member can be given normal pointer like behavior. Such classes will have all the pitfalls of pointers but will require no special copy control.
- The class can implement so-called "smart pointer" behavior. The object to which the pointer points is shared, but the class prevents dangling pointers.
- The class can be given value like behavior. The object to which the pointer points will be unique to and managed separately by each class object.
- In this section we look at three classes that implement each of these different approaches to managing their pointer members.
- A Simple Class with a Pointer Member
- Default Copy/Assignment and Pointer Members
- Classes that have pointer members and use default synthesized copy control have all the pitfalls of ordinary pointers. In particular, the class itself has no way to avoid dangling pointers
- Pointers Share the Same Object
- Dangling Pointers Are Possible
- 13.5.1. Defining Smart Pointer Classes
- An alternative to having a pointer member behave exactly like a pointer is to define what is sometimes referred to as a smart pointer class. A smart pointer behaves like an ordinary pointer except that it adds functionality.
- In this case, we'll give our smart pointer the responsibility for deleting the shared object. Users will dynamically allocate an object and pass the address of that object to our new HasPtr class. The user may still access the object through a plain pointer but must not delete the pointer. The HasPtr class will ensure that the object is deleted when the last HasPtr that points to it is destroyed.
- Our new HasPtr class will need a destructor to delete the pointer. However, the destructor cannot delete the pointer unconditionally. If two HasPtr objects point to the same underlying object, we don't want to delete the object until both objects are destroyed. To write the destructor, we need to know whether this HasPtr is the last one pointing to a given object
- Introducing Use Counts
- A common technique used in defining smart pointers is to use a use count. The pointerlike class associates a counter with the object to which the class points. The use count keeps track of how many objects of the class share the same pointer. When the use count goes to zero, then the object is deleted. A use count is sometimes also referred to as a reference count
- Each time a new object of the class is created, the pointer is initialized and the use count is set to 1. When an object is created as a copy of another, the copy constructor copies the pointer and increments the associated use count. When an object is assigned to, the assignment operator decrements the use count of the object to which the left-hand operand points (and deletes that object if the use count goes to zero) and increments the use count of the object pointed to by the right-hand operand. Finally, when the destructor is called, it decrements the use count and deletes the underlying object if the count goes to zero
- The Use-Count Class
- There are two classic strategies for implementing a use count, one of which we will use here; the other approach is described in Section 15.8.1 (p. 599). In the approach we use here, we'll define a separate concrete class to encapsulate the use count and the associated pointer
- Using the Use-Counted Class
- Assignment and Use Counts
- This assignment operator guards against self-assignment by incrementing the use count of rhs before decrementing the use count of the left-hand operand
- Changing Other Members
- 13.5.2. Defining Value like Classes
- A completely different approach to the problem of managing pointer members is to give them value semantics. Simply put, classes with value semantics define objects that behave like the arithmetic types: When we copy a valuelike object, we get a new, distinct copy. Changes made to the copy are not reflected in the original, and vice versa. The string class is an example of a valuelike class
- The copy constructor no longer copies the pointer. It now allocates a new int object and initializes that object to hold the same value as the object of which it is a copy. Each object always holds its own, distinct copy of its int value. Because each object holds its own copy, the destructor unconditionally deletes the pointer
- As always, the assignment operator must be correct even if we're assigning an object to itself. In this case, the operations are inherently safe even if the left- and right-hand objects are the same. Thus, there is no need to explicitly check for self-assignment
Advice: Managing Pointer Members
Objects with pointer members often need to define the copy-control members. If we rely on the synthesized versions, then the class puts a burden on its users. Users must ensure that the object to which the member points stays around for at least as long as the object that points to it does.
To manage a class with pointer members, we must define all three copy-control members: the copy constructor, assignment operator, and the destructor. These members can define either pointerlike or valuelike behavior for the pointer member.
Valuelike classes give each object its own copy of the underlying values pointed to by pointer members. The copy constructor allocates a new element and copies the value from the object it is copying. The assignment operator destroys the existing object it holds and copies the value from its right-hand operand into its left-hand operand. The destructor destroys the object.
As an alternative to defining either valuelike behavior or pointerlike behavior some classes are so-called "smart pointers." These classes share the same underlying value between objects, thus providing pointerlike behavior. But they use copy-control techniques to avoid some of the pitfalls of regular pointers. To implement smart pointer behavior, a class needs to ensure that the underlying object stays around until the last copy goes away. Use counting (Section 13.5.1, p. 495), is a common technique for managing smart pointer classes. Each copy of the same underlying value is given a use count. The copy constructor copies the pointer from the old object into the new one and increments the use count. The assignment operator decrements the use count of the left-hand operand and increments the count of the right-hand operand. If the use count of the left-hand operand goes to zero, the assignment operator must delete the object to which it points. Finally, the assignment operator copies the pointer from the right-hand operand into its left-hand operand. The destructor decrements the use count and deletes the underlying object if the count goes to zero.
These approaches to managing pointers occur so frequently that programmers who use classes with pointer members must be thoroughly familiar with these programming techniques. |