Answers for chapter 13
Exercise 13.1: What is a copy constructor? When is it used?
A constructor is the copy constructor if its first parameter is a reference to the class type and any additional parameters have default values. A copy constructor will be used in following situations:
=
to initialize a objectpush
, insert
.Exercise 13.2: Explain why the following declaration is illegal:
Sales_data::Sales_data(Sales_data rhs);
illegal. A class copy constructor must take a parameter of type const classType &
.Exercise 13.3: What happens when we copy a StrBlob? What about StrBlobPtrs?
The copying of those classes will be done using memberwise-coping, which means that each non-static member of the object given is copied into the newly created object. If the member is not a built-in type, the element will be copied using its copy constructor.
Additionally, in this special case, any member managed under the shared_ptr will increase its reference count by 1, when a copy is made.
Exercise 13.4: Assuming Point is a class type with a public copy constructor, identify each use of the copy constructor in this program fragment:
Point global;
Point foo_bar(Point arg)
{
Point local = arg, *heap = new Point(global);
*heap = local;
Point pa[ 4 ] = { local, *heap };
return *heap;
}
Fragments use the copy constructor as shown below:
Point foo_bar(Point arg) //passing an object as an argument
Point local = arg; // using an = to initialize a object
*heap = new Point(global); // using an = to initialize a object
*heap = local; // using an = to initialize a object
Point pa[ 4 ] = { local, *heap }; //using brace initialization in an array
return *heap; //returning an object from a function
Exercise 13.5: Given the following sketch of a class, write a copy constructor that copies all the members. Your constructor should dynamically allocate a new string (§ 12.1.2, p. 458) and copy the object to which ps points, rather than copying ps itself.
class HasPtr {
public:
HasPtr(const std::string &s = std::string()):
ps(new std::string(s)), i(0) { }
private:
std::string *ps;
int i;
};
CODE: Q.1
Exercise 13.6: What is a copy-assignment operator? When is this operator used? What does the synthesized copy-assignment operator do? When is it synthesized?
What is a copy-assignment operator?
The copy-assignment operator is a function called operator=
, which is used for assignment from copying an existing object.
When is this operator used?
when assignment is needed.
What does the synthesized copy-assignment operator do?
It assigns each non-static 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.
When is it synthesized?
The compiler generates a synthesized copy-assignment operator for a class if the class does not define its own.
Exercise 13.7: What happens when we assign one StrBlob to another? What about StrBlobPtrs?
It behaves as if it were a copy constructor, which was the case in exercise 13.3.
Exercise 13.8: Write the assignment operator for the HasPtr class from exercise 13.5 in § 13.1.1 (p. 499). As with the copy constructor, your assignment operator should copy the object to which ps points.
CODE: Q.1
Exercise 13.9: What is a destructor? What does the synthesized destructor do? When is a destructor synthesized?
What is a destructor?
A destructor is a special member function that is called when the lifetime of an object ends. The purpose of the destructor is to free the resources that the object may have acquired during its lifetime.
What does the synthesized destructor do?
The members are automatically destroyed after the synthesized destructor body is run.
When is a destructor synthesized?
The compiler defines a synthesized destructor for any class that does not define its own destructor.
Exercise 13.10: What happens when a StrBlob object is destroyed? What about a StrBlobPtr?
All members will be destroyed except for the vector shared by StrBlob and StrBlobPtr: this vector will survive until none of the class objects exist.
Exercise 13.11: Add a destructor to your HasPtr class from the previous exercises.
CODE: Q.1
Exercise 13.12: How many destructor calls occur in the following code fragment?
bool fcn(const Sales_data *trans, Sales_data accum)
{
Sales_data item1(*trans), item2(accum);
return item1.isbn() != item2.isbn();
}
3 times. item1
, item2
, and accum
call the destructor when they leave the scope. trans
will not be taken into account since it is a pointer.
Exercise 13.13: A good way to understand copy-control members and constructors is to define a simple class with these members in which each member prints its name. Add the copy-assignment operator and destructor to X and write a program using X objects in various ways: Pass them as nonreference and reference parameters; dynamically allocate them; put them in containers; and so forth. Study the output until you are certain you understand when and why each copy-control member is used. As you read the output, remember that the compiler can omit calls to the copy constructor.
struct X {
X() {std::cout << "X()" << std::endl;}
X(const X&) {std::cout << "X(const X&)" << std::endl;}
};
CODE: header test file
Exercise 13.14: Assume that numbered is a class with a default constructor that generates a unique serial number for each object, which is stored in a data member named mysn. Assuming numbered uses the synthesized copy-control members and given the following function. what output does the following code produce?
void f (numbered s) { cout << s.mysn << endl; }
numbered a, b = a, c = b;
f(a); f(b); f(c);
All of them will print out the number in a.mysn
, because Synthesized copy-control members copy objects member-wise, such as:
b = a; // b.mysn = a.mysn
c = b; // c.mysn = b.mysn
Exercise 13.15: Assume numbered has a copy constructor that generates a new serial number. Does that change the output of the calls in the previous exercise? If so, why? What output gets generated?
Yes, it changes the output. Because we use a custom copy constructor instead of a synthesized one, as a result, whenever we copy an object, the custom copy constructor will generate a new S/N number and copy it to the newly-created object, such as:
numbered a; // a.mysn = SN1;
b = a; // b.mysn = SN2, SN2 is generated by the copy constructor
c = b; // c.mysn = SN3, SN3 is generated by the copy constructor
Exercise 13.16: What if the parameter in f were const numbered&? Does that change the output? If so, why? What output gets generated?
If we are comparing with the result we have in the mysn
member, No. A const type&
parameter means that the argument is not passed as a copy, so it does not use the copy constructor to construct the object. The local object created by f() will have the same S/N number as its corresponding argument.
Exercise 13.17: Write versions of numbered and f corresponding to the previous three exercises and check whether you correctly predicted the output.
Exercise 13.18: Define an Employee class that contains an employee name and a unique employee identifier. Give the class a default constructor and a constructor that takes a string representing the employee’s name. Each constructor should generate a unique ID by incrementing a static data member.
Exercise 13.19: Does your Employee class need to define its own versions of the copy-control members? If so, why? If not, why not? Implement whatever copy-control members you think Employee needs.
Yes. Sometimes we might need to create a document for an employee based on another employee's file, or a template, instead of creating everything from scratch. Since the synthesized copy-control member overwrites the unique ID whenever a copy-construction or copy-assignment takes place, custom copy-control members are needed to ensure every employee has an unique ID.
CODE: header
test file
Exercise 13.20: Explain what happens when we copy, assign, or destroy objects of our TextQuery and QueryResult classes from § 12.3 (p. 484).
There are two private members in the TextQuery
class. They are both managed by shared_ptr. When they copy or assign, their shared_ptr reference counts will increase by 1. Whenever a TextQUery
object is destroyed, its shared_ptr reference count will decrease by 1.
There are three private members in the QueryResult
class. A copy or assignment of an object will result in the string member being copied to the newly-created object; members managed by shared_ptr will have their reference count increased by 1. When we destroy a QueryResult object, its string member is destroyed as well. The reference counts for members managed by shared_ptr will be decreased by 1.
Exercise 13.21: Do you think the TextQuery and QueryResult classes need to define their own versions of the copy-control members? If so, why? If not, why not? Implement whichever copy-control operations you think these classes require.
No. These classes work well with the synthesized constructor. Furthermore, the copy-control members should be deleted in the classes since logically, a copy or assignment will not make any sense for the classes objects. There is only one TextQuery
object required to deal with one file, and one TextReuslt
object to store the result for a specified keyword. This does not require any copying or assigning operations.
Exercise 13.22: Assume that we want HasPtr to behave like a value. That is, each object should have its own copy of the string to which the objects point. We’ll show the definitions of the copy-control members in the next section. However, you already know everything you need to know to implement these members. Write the HasPtr copy constructor and copy-assignment operator before reading on.
CODE: header
Exercise 13.23: Compare the copy-control members that you wrote for the solutions to the previous section’s exercises to the code presented here. Be sure you understand the differences, if any, between your code and ours.
my strategy to solve the self-assignment:
if (this == &rhs)
return *this;
By doing this, we won't need to worry about deleting ps
causing the object itself to be deleted when assigning it to itself.
Exercise 13.24: What would happen if the version of HasPtr in this section didn’t define a destructor? What if HasPtr didn’t define the copy constructor?
What would happen if the version of HasPtr in this section didn’t define a destructor?
The memory allocated by the ps
will never be freed, a memory leaking occurs.
What if HasPtr didn’t define the copy constructor?
In this case, the synthesized copy constructor will simply copy the string pointer to the newly created object, making their pointer members point to the same string. When both the new object and the original object are destroyed, an undefined result will be presented since the destructor has freed the same string twice.
Exercise 13.25: Assume we want to define a version of StrBlob that acts like a value. Also assume that we want to continue to use a shared_ptr so that our StrBlobPtr class can still use a weak_ptr to the vector. Your revised class will need a copy constructor and copy-assignment operator but will not need a destructor. Explain what the copy constructor and copy-assignment operators must do. Explain why the class does not need a destructor.
In order to copy the entire class content from one StrBlob
object to another, we should not copy the shared_ptr which is used to manage the vector. This is because we need another shared pointer to manage the vector just copied, and even further, to use the shared pointer to work with a StrBlobPtr
class to get the final result.
Therefore, we only need to allocate a new space for the copy of the vector, and then apply a new shared_ptr to manage it.
Due to copy & assignment placing the original StrBlob
object on the right-hand side of the =
, the reference count of its shared_ptr will be decreased to zero. The shared_ptr will be responsible for all object destruction. Therefore, a custom destroyer is not required.
Exercise 13.26: Write your own version of the StrBlob class described in the previous exercise.
Exercise 13.28: Given the following classes, implement a default constructor and the necessary copy-control members.
//a)
class TreeNode {
private:
std::string value;
int count;
TreeNode *left;
TreeNode *right;
};
//b)
class BinStrTree {
private:
TreeNode *root;
};
CODE: header
implement file
Exercise 13.29: Explain why the calls to swap inside swap(HasPtr&, HasPtr&) do not cause a recursion loop.
In this example, we are exchanging built-in types (pointer and int). As we don't define swap() for these types, we call the std::swap()
. Calling the STL overloading version of swap() won't cause recursive loop.
Exercise 13.30: Write and test a swap function for your valuelike version of HasPtr. Give your swap a print statement that notes when it is executed.
Exercise 13.31: Give your class a < operator and define a vector of HasPtrs. Give that vector some elements and then sort the vector. Note when swap is called.
Exercise 13.32: Would the pointerlike version of HasPtr benefit from defining a swap function? If so, what is the benefit? If not, why not?
No. The swap() function could improve performance because it avoids underlying copying by swapping a pointer rather than data. Since our pointlike class is all about exchanging pointers and built-in types, I can't see how it would benefit from swap()
.
Exercise 13.33: Why is the parameter to the save and remove members of Message a Folder&? Why didn’t we define that parameter as Folder? Or const Folder&?
save() and remove() are mutate type members. There are two functions inside these members, insert() and erase() that intend to change the contents of the Folder object. As a result, a reference parameter must be used.
Exercise 13.34: Write the Message class as described in this section.
CODE: header
source
Nots: If the destructor wasn't setup properly for both classes, do not test the function that contains pointer register on both sides (e.g., the save() function). You might get a double free() error if only a set of pointers was unregistered from another. I guess the std::set::~set() might have done something with deleting the pointer that we hadn't unregistered. ( Will back to check the problem)
Exercise 13.35: What would happen if Message used the synthesized versions of the copy-control members?
The relationship between a message and a folder is maintained by two sets of pointers. Synthesized copy members cannot keep track of these ties due to the fact that they cannot update the pointers. The accessing operation will run into serious problems if the relationship is not updated properly.
Suppose we deleted a message and didn't clean the pointer of the message in every folder, we could still see the message existed in the folder after deleting, but accessing it by the pointer would be an undefined behavior.
Exercise 13.36: Design and implement the corresponding Folder class. That class should hold a set that points to the Messages in that Folder.
Exercise 13.37: Add members to the Message class to insert or remove a given Folder* into folders. These members are analogous to Folder’s addMsg and remMsg operations.
Please check the files in 13.36.
Exercise 13.38: We did not use copy and swap to define the Message assignment operator. Why do you suppose this is so?
In comparison to the copy-assignment operator, the copy-swap operator is more expensive. By far, the biggest additional cost comes from the pointer register:
Ultimately, the copy-swap operator takes five loops to take care of the pointer register; in contrast, the copy-assignment operation takes only two loops, not to mention the possibly expensive in using std::swap
. Although the copy-swap operator is capable here, it is not worthwhile to have one in this case.
Exercise 13.39: Write your own version of StrVec, including versions of reserve, capacity (§ 9.4, p. 356), and resize (§ 9.3.5, p. 352).
Exercise 13.40: Add a constructor that takes an initializer_list<string> to your StrVec class.
Exercise 13.41: Why did we use postfix increment in the call to construct inside push_back? What would happen if it used the prefix increment?
As we use the prefix version, the element pointed by the off-the-end pointer won't be constructed, since the pointer moves to the right first. <html>
<img src=“/_media/programming/cpp/cpp_primer/answers/ex_13_41.svg” width=“400”>
</html>
Exercise 13.42: Test your StrVec class by using it in place of the vector<string> in your TextQuery and QueryResult classes (§ 12.3, p. 484).
CODE: header TextQuery.h source test file
Exercise 13.43: Rewrite the free member to use for_each and a lambda (§ 10.3.2, p. 388) in place of the for loop to destroy the elements. Which implementation do you prefer, and why?
It is my preferred version, since the loop order doesn't matter in this version. In addition, it is more readable and makes the resource-free process much easier to understand.
CODE: header
source test file
Exercise 13.44: Write a class named String that is a simplified version of the library string class. Your class should have at least a default constructor and a constructor that takes a pointer to a C-style string. Use an allocator to allocate memory that your String class uses.
Exercise 13.45: Distinguish between an rvalue reference and an lvalue reference.
* lvalue, emphasizing the identity, is usually an object that can be addressed.
Therefore, the difference between an rvalue reference and an lvalue reference is that an rvalue reference refers to an object that is about to expire, whereas an lvalue reference refers to one that will exist for its entirety.
Exercise 13.46: Which kind of reference can be bound to the following initializers?
int f();
vector<int> vi(100);
//&&, f() returns by value, which yields a rvalue
int? r1 = f();
//&, vi[0] subscript operation yields a lvalue
int? r2 = vi[0];
//&, value expression is a lvalue
int? r3 = r1;
//&&, an arithmetic operation yields a rvalue
int? r4 = vi[0] * f();
Exercise 13.47: Give the copy constructor and copy-assignment operator in your String class from exercise 13.44 in § 13.5 (p. 531) a statement that prints a message each time the function is executed.
Exercise 13.48: Define a vector<String> and call push_back several times on that vector. Run your program and see how often Strings are copied.
vector<String> myVS;
myVS.push_back("a"); // 1 time
myVS.push_back("hello"); // 3 times
myVS.push_back("World"); // 6 times
myVS.push_back("Cpp"); // 7 times
myVS.push_back("Yes"); // 12 times
Looks like the program calls copy constructor each time we do a push_back(). Besides, If reallocation occurs, the copy quantity will be doubled.
Exercise 13.49: Add a move constructor and move-assignment operator to your StrVec, String, and Message classes.
Message : header
source test file
String: header
source test file
StrVec: header
source test file
Exercise 13.50: Put print statements in the move operations in your String class and rerun the program from exercise 13.48 in § 13.6.1 (p. 534) that used a vector<String> to see when the copies are avoided.
vector<String> myVS;
myVS.push_back("a"); // 1 time
myVS.push_back("hello"); // 1 time
myVS.push_back("World"); // 1 time
myVS.push_back("Cpp"); // 1 time
myVS.push_back("Yes"); // 1 time
Looks like reallocate() takes move constructor in place of copy constructor. The push_back() takes a const String&
argument so that it calls copy constructor.
Exercise 13.51: Although unique_ptrs cannot be copied, in § 12.1.5 (p. 471) we wrote a clone function that returned a unique_ptr by value. Explain why that function is legal and how it works.
unique_ptr<int> clone(int p) {
unique_ptr<int> ret(new int (p));
// . . .
return ret;
}
The entire process calls only the move constructor. The clone() takes an rvalue argument, so the move constructor moves the content of p
to ret
; The clone() returns an rvalue, so the compiler “moves” ret
as a return value. In this process, there is no copy involved. Therefore, it is legal.
Exercise 13.52: Explain in detail what happens in the assignments of the HasPtr objects on page 541. In particular, describe step by step what happens to values of hp, hp2, and of the rhs parameter in the HasPtr assignment operator.
class HasPtr {
public:
HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) {p.ps = 0;}
HasPtr& operator=(HasPtr rhs)
{ swap(*this, rhs); return *this; }
};
//1. a temporary HasPtr is allocated, as the copy of hp2
//2. hp is freed
//3. hp and hp2(copy) are swapped, now the hp own the copy
hp = hp2;
//1.hp is freed, since move constructor doesn't allocate new space
//2.hp and the copy are swapped. After swapping, hp now points to the memory that used to owned by hp2
//3.The pointer in hp2(copy) is nulled for the destructor.
hp = std::move(hp2);
Exercise 13.53: As a matter of low-level efficiency, the HasPtr assignment operator is not ideal. Explain why. Implement a copy-assignment and move-assignment operator for HasPtr and compare the operations executed in your new move-assignment operator versus the copy-and-swap version.
suppose we have the following test operations:
HasPtr hp("Hello");
HasPtr hp2, hp3;
hp2 = hp;
hp3 = std::move(hp);
using copy-swap assignment operator will:
hp2 = hp
;hp3 = std::move(hp);
using copy-assignment operator plus move-assignment operator will:
hp2 = hp;
'hp3 = std::move(hp);
We can see that every time we do a copy-swap assignment, it invokes the copy / move constructor. This is because the copy-swap operator takes an argument, passes it to the rhs
, then passes the rhs
to the swap()
. We can avoid the middle stage if we overload assignment operators instead.
CODE : header
test
Test output:
/* copy-swap */
Copy-CSTR called.
Copy-swap OP called.
Move-CSTR called.
Copy-swap OP called.
/* copy assign plus move assign */
Copy-assignment OP called.
Move-assignment OP called.
Exercise 13.54: What would happen if we defined a HasPtr move-assignment operator but did not change the copy-and-swap operator? Write code to test your answer.
It will result in an ambiguous error. It is likely that there will be two best match candidates of =
that take an argument of type HasPtr
as an argument if we do not change the copy-swap.
Exercise 13.55: Add an rvalue reference version of push_back to your StrBlob.
inline void
strBlob::push_back (std::string &&ele)
{
data->push_back(std::move(ele));
}
Exercise 13.56: What would happen if we defined sorted as:
Foo Foo::sorted() const &
{
Foo ret(*this);
return ret.sorted();
}
The function is expecting to return an rvalue, thus it needs to call the ravlue version of sorted()
to end the recursive loop. However, every time will run Foo ret(*this)
, we get a brand new lvalue object if Foo
type. ret.sorted()
resulted in a new call to create a new lvaule object of Foo
type that will keep calling the lvalue version of the sorted()
again and again. As a result, this program will never end and eventually end up with a stack overflow.
Exercise 13.57: What if we defined sorted as:
Foo Foo::sorted() const & { return Foo(*this).sorted(); }
This version works well. In this case, Foo(*this)
is an rvalue that will invoke the rvalue version of sorted()
,which yields an rvalue object of type Foo
.