Copy Control Flashcards
When does initialzation occur
Copy initialization happens not only when we define variables using an =, but also when we
• Pass an object as an argument to a parameter of nonreference type
• Return an object from a function that has a nonreference return type
• Brace initialize the elements in an array or the members of an aggregate class
Some class types also use copy initialization for the objects they allocate. For example, the library containers copy initialize their elements when we initialize the container, or when we call an insert or push member (§ 9.3.1, p. 342). By contrast, elements created by an emplace member are direct initialized
How does explicit keyword constraints on copy initialization
vector v1(10); // ok: direct initialization vector v2 = 10; // error: constructor that takes a size is explicit void f(vector); // f's parameter is copy initialized f(10); // error: can't use an explicit constructor to copy an argument f(vector(10)); // ok: directly construct a temporary vector from an int
Directly initializing v1 is fine, but the seemingly equivalent copy initialization of v2 is an error, because the vector constructor that takes a single size parameter is explicit. For the same reasons that we cannot copy initialize v2, we cannot implicitly use an explicit constructor when we pass an argument or return a value from a function.
Can compiler bypass copy ctor?
During copy initialization, the compiler is permitted (but not obligated) to skip the copy/move constructor and create the object directly.
string null_book = "9-999-99999-9"; // copy initialization to string null_book("9-999-99999-9"); // compiler omits the copy constructor
However, even if the compiler omits the call to the copy/move constructor, the copy/move constructor must exist and must be accessible (e.g., not private) at that point in the program.
What is the best practise for the return value of a assignment operator
It is also worth noting that the library generally requires that types stored in a container have assignment operators that return a reference to the left-hand operand.
What are synthesized copy-assignment operator
The compiler generates a synthesized copyassignment operator for a class if the class does not define its own. Analogously to the copy constructor, for some classes the synthesized copy-assignment operator disallows assignment. Otherwise, it assigns each nonstatic member of the right-hand object to the corresponding member of the left-hand object using the copy-assignment operator for the type of that member. Array members are assigned by assigning each element of the array.
What is the order of destruction for members in a class inside a destructor
In a destructor, the function body is executed first and then the members are destroyed. Members are destroyed in reverse order from the order in which they were initialized.
When is a Destructor called?
The destructor is used automatically whenever an object of its type is destroyed:
• Variables are destroyed when they go out of scope.
• Members of an object are destroyed when the object of which they are a part is destroyed.
• Elements in a container—whether a library container or an array—are destroyed when the container is destroyed.
• Dynamically allocated objects are destroyed when the delete operator is applied to a pointer to the object.
• Temporary objects are destroyed at the end of the full expression in which the temporary was created.
Why does a class with a user defined destructor almost certainly needs a copy ctor and copy-assignment operator
Consider what would happen if we gave HasPtr a destructor but used the synthesized versions of the copy constructor and copy-assignment operator:
class HasPtr { public: HasPtr(const std::string &s = std::string()): ps(new std::string(s)), i(0) { } ~HasPtr() { delete ps; } // WRONG: HasPtr needs a copy constructor and copy-assignment operator // other members as before };
In this version of the class, the memory allocated in the constructor will be freed when a HasPtr object is destroyed. Unfortunately, we have introduced a serious bug! This version of the class uses the synthesized versions of copy and assignment. Those functions copy the pointer member, meaning that multiple HasPtr objects may be pointing to the same memory:
HasPtr f(HasPtr hp) // HasPtr passed by value, so it is copied { HasPtr ret = hp; // copies the given HasPtr // process ret return ret; // ret and hp are destroyed }
When f returns, both hp and ret are destroyed and the HasPtr destructor is run on each of these objects. That destructor will delete the pointer member in ret and in hp. But these objects contain the same pointer value. This code will delete that pointer twice, which is an error
HasPtr p("some values"); f(p); // when f completes, the memory to which p.ps points is freed HasPtr q(p); // now both p and q point to invalid memory!
The memory to which p (and q) points is no longer valid. It was returned to the system when hp (or ret!) was destroyed.
What is the syntax to hint a compiler to provide a synthesized member function
We can explicitly ask the compiler to generate the synthesized versions of the copycontrol members by defining them as = default:
// copy control; use defaults Sales_data() = default;
We can use = default only on member functions that have a synthesized version (i.e., the default constructor or a copy-control member).
How to mark a member function as delete-ed
Under the new standard, we can prevent copies by defining the copy constructor and copy-assignment operator as deleted functions.
NoCopy() = default; // use the synthesized default constructor
NoCopy(const NoCopy&) = delete; // no copy
Unlike = default, = delete must appear on the first declaration of a deleted function.
Also unlike = default, we can specify = delete on any function (we can use = default only on the default constructor or a copy-control member that the compiler can synthesize). Although the primary use of deleted functions is to suppress the copy-control members, deleted functions are sometimes also useful when we want to guide the function-matching process.
What is the harm in delete-ing a destructor?
It is not possible to define an object or delete a pointer to a dynamically allocated object of a type with a deleted destructor.
When are synthesized copy ctor or copy assignment operator as deleted?
For some classes, the compiler defines these synthesized members as deleted functions: • The synthesized destructor is defined as deleted if the class has a member whose own destructor is deleted or is inaccessible (e.g., private). • The synthesized copy constructor is defined as deleted if the class has a member whose own copy constructor is deleted or inaccessible. It is also deleted if the class has a member with a deleted or inaccessible destructor. • The synthesized copy-assignment operator is defined as deleted if a member has a deleted or inaccessible copy-assignment operator, or if the class has a const or reference member. • The synthesized default constructor is defined as deleted if the class has a member with a deleted or inaccessible destructor; or has a reference member that does not have an in-class initializer or has a const member whose type does not explicitly define a default constructor and that member does not have an in-class initializer.
In essence, these rules mean that if a class has a data member that cannot be default constructed, copied, assigned, or destroyed, then the corresponding member will be a deleted function. It may be surprising that a member that has a deleted or inaccessible destructor causes the synthesized default and copy constructors to be defined as deleted. The reason for this rule is that without it, we could create objects that we could not destroy.
It should not be surprising that the compiler will not synthesize a default constructor for a class with a reference member or a const member that cannot be default constructed. Nor should it be surprising that a class with a const member cannot use the synthesized copy-assignment operator
How to “copy-control” pre-c++11
Prior to the new standard, classes prevented copies by declaring their copy constructor
and copy-assignment operator as private:
class PrivateCopy { // no access specifier; following members are private by default // copy control is private and so is inaccessible to ordinary user code PrivateCopy(const PrivateCopy&); PrivateCopy &operator=(const PrivateCopy&); // other members public: PrivateCopy() = default; // use the synthesized default constructor ~PrivateCopy(); // users can define objects of this type but not copy them };
Because the destructor is public, users will be able to define PrivateCopy objects. However, because the copy constructor and copy-assignment operator are private, user code will not be able to copy such objects. However, friends and members of the class can still make copies. To prevent copies by friends and members, we declare these members as private but do not define them.
Give example for a value-like copy-control for class which dynamically allocates a data member
Assignment operators typically combine the actions of the destructor and the copy constructor.
It is crucially important that these actions be done in a sequence that is correct even if an object is assigned to itself. Moreover, when possible, we should also write our assignment operators so that they will leave the left-hand operand in a sensible state should an exception occur
Make our code safe should an exception happen—by first copying the right-hand side. After the copy is made, we’ll free the left-hand side and update the pointer to point to the newly allocated string:
HasPtr& HasPtr::operator=(const HasPtr &rhs) { auto newp = new string(*rhs.ps); // copy the underlying string delete ps; // free the old memory ps = newp; // copy data from rhs into this object i = rhs.i; return *this; // return this object }
- Assignment operators must work correctly if an object is assigned to itself.
- Most assignment operators share work with the destructor and copy constructor.
A good pattern to use when you write an assignment operator is to first copy the right-hand operand into a local temporary. After the copy is done, it is safe to destroy the existing members of the left-hand operand. Once the lefthand operand is destroyed, copy the data from the temporary into the members of the left-hand operand.
To illustrate the importance of guarding against self-assignment, consider what would happen if we wrote the assignment operator as
// WRONG way to write an assignment operator! HasPtr& HasPtr::operator=(const HasPtr &rhs) { delete ps; // frees the string to which this object points // if rhs and *this are the same object, we're copying from deleted memory! ps = new string(*(rhs.ps)); i = rhs.i; return *this; }
It is crucially important for assignment operators to work correctly, even when an object is assigned to itself. A good way to do so is to copy the right-hand operand before destroying the left-hand operand.
How does reference counting work
Reference counting works as follows:
• In addition to initializing the object, each constructor (other than the copy constructor) creates a counter. This counter will keep track of how many objects share state with the object we are creating. When we create an object, there is only one such object, so we initialize the counter to 1.
• The copy constructor does not allocate a new counter; instead, it copies the data members of its given object, including the counter. The copy constructor increments this shared counter, indicating that there is another user of that object’s state.
• The destructor decrements the counter, indicating that there is one less user of the shared state. If the count goes to zero, the destructor deletes that state.
• The copy-assignment operator increments the right-hand operand’s counter and decrements the counter of the left-hand operand. If the counter for the left-hand operand goes to zero, there are no more users. In this case, the copyassignment operator must destroy the state of the left-hand operand.