What is a copy constructor? When is it used?
A copy constructor is a constructor which first parameter is a reference to the class type and any additional parameters have default values.
When copy initialization happens and that copy initialization requires either the copy constructor or the move constructor.
- Define variables using an
=
- 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.
Explain why the following declaration is illegal:
Sales_data::Sales_data(Sales_data rhs);
If declaration like that, the call would never succeed to call the copy constructor, Sales_data rhs
is an argument to a parameter, thus, we'd need to use the copy constructor to copy the argument, but to copy the argument, we'd need to call the copy constructor, and so on indefinitely.
What happens when we copy a
StrBlob
? What aboutStrBlobPtrs
?
// added a public member function to StrBlob and StrBlobPrts
long count() {
return data.use_count(); // and wptr.use_count();
}
// test codes in main()
StrBlob str({"hello", "world"});
std::cout << "before: " << str.count() << std::endl; // 1
StrBlob str_cp(str);
std::cout << "after: " << str.count() << std::endl; // 2
ConstStrBlobPtr p(str);
std::cout << "before: " << p.count() << std::endl; // 2
ConstStrBlobPtr p_cp(p);
std::cout << "after: " << p.count() << std::endl; // 2
when we copy a StrBlob
, the shared_ptr
member's use_count add one.
when we copy a StrBlobPrts
, the weak_ptr
member's use_count isn't changed.(cause the count belongs to shared_ptr
)
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) // 1. Pass an object as an argument to a parameter of nonreference type
{
Point local = arg, *heap = new Point(global); // 2.3. copy initialization
*heap = local; // 4. Define variables using an `=`
Point pa[ 4 ] = { local, *heap }; // 5. Brace initialize the elements in an array
return *heap; // 6. Return an object from a function that has a nonreference return type
}
What is a copy-assignment operator? When is this operator used? What does the synthesized copy-assignment operator do? When is it synthesized?
The copy-assignment operator is function named operator=
.
This operator is used when assignment occurred.
The synthesized copy-assignment operator assigns each nonstatic member of the right-hand object to corresponding member of the left-hand object using the copy-assignment operator for the type of that member.
It is synthesized when the class does not define its own.
What happens when we assign one StrBlob to another? What about StrBlobPtrs?
In both cases, shallow copy will happen. All pointers point to the same address. The use_count
changed the same as 13.3.
What is a destructor? What does the synthesized destructor do? When is a destructor synthesized?
The destructor is a member function with the name of the class prefixed by a tilde(~).
As with the copy constructor and the copy-assignment operator, for some classes, the synthesized destructor is defined to disallow objects of the type from being destoryed. Otherwise, the synthesized destructor has an empty function body.
The compiler defines a synthesized destructor for any class that does not define its own destructor.
What happens when a StrBlob object is destroyed? What about a StrBlobPtr?
When a StrBlob
object destroyed, the use_count
of the dynamic object will decrement. It will be freed if no shared_ptr
to that dynamic object.
When a StrBlobPter
object is destroyed the object dynamicaly allocated will not be freed.
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. There are accum
, item1
and item2
.
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 namedmysn
. Assuming numbered uses the synthesized copy- control members and given the following function:
void f (numbered s) { cout << s.mysn << endl; }
what output does the following code produce?
numbered a, b = a, c = b;
f(a); f(b); f(c);
Three identical numbers.
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, the output will change. cause we don't use the synthesized copy-control members rather than own defined.The output will be three different numbers.
What if the parameter in f were const numbered&? Does that change the output? If so, why? What output gets generated?
Yes, the output will change. cause the function f
haven't any copy operators. Thus, the output are the same when pass the each object to f
.
Explain what happens when we copy, assign, or destroy objects of our TextQuery and QueryResult classes from § 12.3 (p. 484).
The member (smart pointer and container) will be copied.
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.
(@Mooophy) No copy-control members needed.
Because, all these classes are using smart pointers to manage dynamic memory which can be freed automatically by calling synthesized destructors. The objects of these classes should share the same dynamic memory.Hence no user-defined version needed as well.
TextQuery(const TextQuery&) = delete;
TextQuery& operator=(const TextQuery) = delete;
QueryResult(const QueryResult&) = delete;
QueryResult& operator=(const QueryResult) = delete;
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.
Check 13.22.
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?
If HasPtr
didn't define a destructor, memory leak will happened. If HasPtr
didn't define the copy constructor, when assignment happened, just points copied, the string witch ps
points haven't been copied.
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 copyassignment operators must do. Explain why the class does not need a destructor.
Copy constructor and copy-assignment operator should dynamicly allocate memory for its own , rather than share the object with the right hand operand.
StrBlob
is using smart pointers which can be managed with synthesized destructor, If an object of StrBlob
is out of scope, the destructor for std::shared_ptr will be called automaticaly to free the memory dynamically allocated when the use_count
goes to 0.
Explain why the calls to swap inside swap(HasPtr&, HasPtr&) do not cause a recursion loop.
swap(lhs.ps, rhs.ps);
feed the version : swap(std::string*, std::string*)
and swap(lhs.i, rhs.i);
feed the version : swap(int, int)
. Both them can't call swap(HasPtr&, HasPtr&)
. Thus, the calls don't cause a recursion loop.
Would the pointerlike version of
HasPtr
benefit from defining a swap function? If so, what is the benefit? If not, why not?
@Mooophy:
Essentially, the specific avoiding memory allocation is the reason why it improve performance. As for the pointerlike version, no dynamic memory allocation anyway. Thus, a specific version for it will not improve the performance.
Why is the parameter to the
save
andremove
members of Message a Folder&? Why didn’t we define that parameter asFolder
? Orconst Folder&
?
Because these operations must also update the given Folder
. Updating a Folder
is a job that the Folder
class controls through its addMsg
and remMsg
members, which will add or remove a pointer to a given Message
, respectively.
What would happen if
Message
used the synthesized versions of the copy-control members?
some existing Folders
will out of sync with the Message
after assignment.
We did not use copy and swap to define the Message assignment operator. Why do you suppose this is so?
@Mooophy The copy and swap is an elegant way when working with dynamicly allocated memory. In the Message class ,noing is allocated dynamically. Thus using this idiom makes no sense and will make it more complicated to implement due to the pointers that point back.
@pezy
In this case, swap
function is special. It will be clear two Message
's folders , then swap members, and added themselves to each folders. But, Message
assignment operator just clear itself, and copy the members, and added itself to each folders. The rhs
don't need to clear and add to folders. So, if using copy and swap to define, it will be very inefficiency.
Why did we use postfix increment in the call to construct inside push_back? What would happen if it used the prefix increment?
|a|b|c|d|f|..............|
^ ^ ^
elements first_free cap
// if use alloc.construct(first_free++, "g");
|a|b|c|d|f|g|.............|
^ ^ ^
elements first_free cap
// if use alloc.construct(++first_free, "g");
|a|b|c|d|f|.|g|............|
^ ^ ^ ^
elements | first_free cap
|
"unconstructed"
Test your StrVec class by using it in place of the vector in your TextQuery and QueryResult classes (12.3, p. 484).
- StrVec : hpp | cpp
- TextQuery and QueryResult : hpp | cpp
- Text : ex13_42.cpp
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?
Rewrite
for_each(elements, first_free, [this](std::string &rhs){ alloc.destroy(&rhs); });
@Mooophy: The new version is better. Compared to the old one, it doesn't need to worry about the order and decrement.So more straightforward and handy. The only thing to do for using this approach is to add "&" to build the pointers to string pointers.
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.
more information to see A trivial String class that designed for write-on-paper in an interview
Distinguish between an rvalue reference and an lvalue reference.
Definition:
- lvalue reference: reference that can bind to an lvalue. (Regular reference)
- rvalue reference: reference to an object that is about to be destroyed.
We can bind an rvalue reference to expression that require conversion, to literals, or to expressions that return an rvalue, but we cannot directly bind an rvalue reference to an lvalue.
int i = 42;
int &r = i; // lvalue reference
int &&rr = i; // rvalue reference (Error: i is a lvalue)
int &r2 = i*42; // lvalue reference (Error: i*42 is a rvalue)
const int &r3 = i*42; // reference to const (bind to a rvalue)
int &&rr2 = i*42; // rvalue reference
- lvalue : functions that return lvalue references, assignment, subscript, dereference, and prefix increment/decrement operator.
- rvalue / const reference : functions that return a nonreferences, arithmetic, relational bitwise, postfix increment/decrement operators.
Which kind of reference can be bound to the following initializers?
int f();
vector<int> vi(100);
int&& r1 = f();
int& r2 = vi[0];
int& r3 = r1;
int&& r4 = vi[0] * f();