C++ Primer 0x0D exercise solution

Posted by intodesi on Wed, 26 Jan 2022 21:41:36 +0100

📔 C++ Primer 0x0D exercise solution

Better reading experience (real-time update and correction)

It is recommended to read the summary of knowledge points of "C++ Primer 5th" & exercise problem solution

13.1 copy, assignment and destruction

13.1.1 copy constructor

13.1 what is a copy constructor? When to use it?

If the first parameter of a constructor is a reference to its own class type (and generally a reference to const), and any additional parameters have default values, this constructor is a copy constructor

The copy constructor is used when the copy is initialized

Copy initialization occurs not only when we define variables with =, but also in the following cases

  • Pass an object as an argument to a formal parameter of a non reference type
  • Returns an object from a function whose return type is a non reference type
  • Initializes elements in an array or members in an aggregate class with a curly brace list
  • Some types also use copy initialization for their assigned objects (insert and push will copy initialization, and empty will initialize directly)

13.2 explain why the following statement is illegal:

Sales_data::Sales_data(Sales_data rhs);

The parameter should be a reference of its own type

13.3 what happens when we copy a StrBlob? Copy a StrBlobPtr?

StrBlob uses shared_ptr reference count plus one

StrBlobPtr uses weak_ptr reference count does not increase

Direct copy of other data members

13.4 assuming that Point is a class type and has a public copy constructor, indicate where the copy constructor is used in the following program fragments:

Point global;
Point foo_bar(Point arg) // 1
{
	Point local = arg, *heap = new Point(global); // 2,3
	*heap = local; //4
	Point pa[4] = { local, *heap }; // 5, 6
	return *heap;  // 7
}
  • Pass an object as an argument to a formal parameter of a non reference type (1)
  • Use = to define variables (2,3,4)
  • Initializes elements in an array or members in an aggregate class with a curly bracket list (used twice in 5 and 6)
  • Return an object from a function whose return type is non reference type (7)

13.5 given the following class framework, write a copy constructor to copy all members. Your constructor should dynamically allocate a new string and copy the object to the location pointed by ps instead of 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;
}
#include <string>

class HasPtr {
public:
	HasPtr(const std::string& s = std::string()):
		ps(new std::string(s)), i(0) { }
	HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){}
private:
	std::string *ps;
	int i;
};

13.1.2 copy assignment operator

13.6 what is the copy assignment operator? When to use it? What does the composite copy assignment operator do? When will the composite copy assignment operator be generated?

  • The copy operator is an overload of the function operator =, and the copy assignment operator accepts a parameter of the same type as its class

The copy assignment operator is used when the assignment operation is used

Composite copy assignment operator

  • If a class does not define its own copy assignment operator, the compiler will produce a composite copy assignment operator for it
  • For some classes, the composite copy assignment operator is used to prohibit the assignment of objects of this type
  • Generally, the copy assignment operator will assign each non static member of the right operand to the corresponding member of the left operand. This is done through the copy assignment operator of member type. For members of array type, assign array elements one by one
  • The composite copy assignment operator returns a reference to the left operand

13.7 what happens when we assign one StrBlob to another? What about StrBlobPtr?

StrBlob: through shared_ The copy assignment operator of PTR class will be shared_ptr copy assignment, and its counter increases automatically.

StrBlobPtr: pass weak_ The copy assignment operator of PTR class will break_ PTR copy assignment. curr calls the assignment operator of the built-in type.

13.8 write assignment operators for the HasPtr class in exercise 13.5 in section 13.1.1. Similar to the copy constructor, your assignment operator should copy the object to the position pointed to by ps.

Remember to deal with self assignment and exception safety. This is the way to write the class value copy assignment operator

#include <string>

class HasPtr {
public:
	HasPtr(const std::string& s = std::string()):
		ps(new std::string(s)), i(0) { }
	HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){}
	HasPtr& operator=(const HasPtr& rhs){
		auto newp = new std::string(*rhs.ps);
		delete ps;
		ps = newp;
		i = rhs.i;
		return *this;
	}
    ~HasPtr() { delete ps;} 
private:
	std::string *ps;
	int i;
};

13.9 what is a destructor? What does the synthetic destructor do? When will the composite destructor be generated?

  • Destructor: releases the resources used by the object and destroys the non static data members of the object
  • A destructor is a member function of a class. Its name is tilde followed by the class name. It has no return value and does not accept parameters. It cannot be overloaded and is unique to a given class

What does the destructor do

  • In the destructor, the function body is executed first, and then the members are destroyed. The members are destroyed in the reverse order of initialization
  • Unlike constructors with initialization lists, the destructor is implicit. What happens when a member is destroyed depends entirely on the type of the member
    • Destroy the members of the class type and execute the destructor of the class type
    • There is no destructor for built-in types. There is no need to do anything to destroy built-in type members
    • Implicitly destroying a member of a built-in pointer type does not delete the object it points to
    • Unlike ordinary pointers, smart pointers are class types, so they have destructors. Smart pointer members are automatically destroyed during the destruct phase

Synthetic destructor

  • When a class does not define its own destructor, the compiler will define a composite destructor for it
  • For some classes, composite destructors are used to prevent objects of that type from being destroyed
  • Generally, the function body of the synthetic destructor is empty
  • **It is important to recognize that the destructor itself does not directly destroy members. Members are destroyed in the implicit destructor stage after the destructor body** In the whole object destruction process, the destructor body is carried out as another part besides the member destruction step

13.10 what happens when a StrBlob object is destroyed? When a StrBlobPtr object is destroyed?

StrBlob: shared_ The reference count of PTR decreases

StrBlobPtr: does not affect reference count

13.11 add a destructor to the HasPtr class in the previous exercise.

#include <string>

class HasPtr {
public:
	HasPtr(const std::string& s = std::string()):
		ps(new std::string(s)), i(0) { }
	HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){}
	HasPtr& operator=(const HasPtr& rhs){
		auto newp = new std::string(*rhs.ps);
		delete ps;
		ps = newp;
		i = rhs.i;
		return *this;
	}
	~HasPtr(){delete ps;}
private:
	std::string *ps;
	int i;
};

13.12 how many destructor calls will occur in the following code snippet?

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 leave the scope and are destroyed

trans is a pointer to an object, and the destructor will not execute

13.13 a good way to understand copy control members and constructors is to define a simple class, define these members for this class, and each member prints its own name:

struct X {
	X() {std::cout << "X()" << std::endl;}
	X(const X&) {std::cout << "X(const X&)" << std::endl;}
};

Add copy assignment operators and destructors to x, and write a program to use the objects of X in different ways: pass them as non reference parameters; Dynamically allocate them; Store them in containers; and so on. Observe the output of the program until you are sure you understand when copy control members are used and why they are used. When you look at the program output, remember that the compiler can skip calls to the copy constructor.

#include <iostream>
#include <string>
#include <vector>

struct X {
	X() {std::cout << "X()" << std::endl;}
	X(const X&) {std::cout << "X(const X&)" << std::endl;}
	X& operator=(const X&rhs){std::cout << "X& operator=(const X&rhs)" << std::endl;}
	~X(){std::cout << "~X()" << std::endl;}
};
void f(X a,const X& b){
	std::vector<X>v;
	std::cout << "push_back a" << std::endl;
	v.push_back(a);
	std::cout << "push_back b" << std::endl;
	v.push_back(b);


}
int main(){
	std::cout << "create pb" << std::endl;
	X a;

	std::cout << "create pb" << std::endl;
	X* pb = new X(a);

	std::cout << "f(a,*pb)" << std::endl;
	f(a,*pb);
	delete pb;


	
	return 0;
}

13.1.4 three / five rule

13.14 suppose numbered is a class with a default constructor, which can generate a unique sequence number for each object and store it in the data member named mysn. Suppose numbered uses the combined copy control member and gives the following function:

void f (numbered s) { cout << s.mysn < endl; }

What is the output of the following code?

numbered a, b = a, c = b;
f(a); f(b); f(c);

Output three identical numbers

13.15 suppose numbered defines a copy constructor, which can generate a new serial number. Will this change the output of the call in the last question? If it will change, why? What is the new output?

Output three different numbers, but it has nothing to do with a, B and C

13.16 what happens if the parameter in f is const numbered &? Does this change the output? If it will change, why? What is the new output?

Output three different numbers: A, B and C

13.17 write numbered and f described in the first three questions respectively to verify whether you have correctly predicted the output results.

3.14

#include <iostream>
#include <string>
#include <vector>

class numbered{
public:
	friend void f(numbered s);
	numbered() {mysn = num++;}
private:
	static int num;
	int mysn;
};

int numbered::num = 0;

void f (numbered s) { std::cout << s.mysn << std::endl; }

int main(){
	numbered a,b=a,c=b;
	f(a);f(b);f(c);
	return 0;
}

Output three zeros

3.15

#include <iostream>
#include <string>
#include <vector>

class numbered{
public:
	friend void f(numbered s);
	numbered() {mysn = num++;}
	numbered(const numbered &s){
		mysn = num++;
	}
private:
	static int num;
	int mysn;
};

int numbered::num = 0;

void f (numbered s) { std::cout << s.mysn << std::endl; }

int main(){
	numbered a,b=a,c=b;
	f(a);f(b);f(c);
	return 0;
}

Output 3,4,5

3.16

#include <iostream>
#include <string>
#include <vector>

class numbered{
public:
	friend void f(const numbered &s);
	numbered() {mysn = num++;}
	numbered(const numbered &s){
		mysn = num++;
	}
private:
	static int num;
	int mysn;
};

int numbered::num = 0;

void f (const numbered &s) { std::cout << s.mysn << std::endl; }

int main(){
	numbered a,b=a,c=b;
	f(a);f(b);f(c);
	return 0;
}

Output 0,1,2

13.1.6 prevent copying

3.18 define an Employee class, which contains the Employee's name and unique Employee ID number. Define a default constructor for this class and a constructor that accepts a string representing the Employee's name. Each constructor should generate a unique license number by incrementing a static data member.

class Employee{
public:
	Employee(){id = id_num++;}
	Employee(const std::string &s):name(s),id(id_num){
		id_num++;
	}

private:
	std::string name;
	int id;
	static int id_num;
};
static int id_num = 1000;

3.19 does your Employee class need to define its own copy control members? If so, why? If not, why? Implement the copy control members you think employees need.

There is no need to copy, and employees do not have to increment the number because of the copy. The copy construction and assignment construction operators are defined as delete to prevent copy and assignment

class Employee{
public:
	Employee(){id = id_num++;}
	Employee(const std::string &s):name(s),id(id_num){
		id_num++;
	}
	Employee(const Employee &)=delete;
	Employee& operator=(const Employee&)=delete;

private:
	std::string name;
	int id;
	static int id_num;
};
static int id_num = 1000;

13.20 explain what happens when we copy, assign, or destroy TextQuery and QueryResult class objects?

Because smart pointers are used in these two classes, all members of the class will be copied when copying, and all members will be destroyed when destroying.

13.21 do you think TextQuery and QueryResult classes need to define copy control members of their own versions? If so, why? Implement the copy control operations you think these two classes need.

No, because of the smart pointer, the composite version can automatically control the memory release

When we decide whether a class needs to define its own version of copy control members, a basic principle is to determine whether the class needs a destructor, and the classes that need a destructor also need copy and assignment operations

13.2 copy control and resource management

13.22 suppose we want HasPtr to behave like a value. That is, for the string member pointed to by the object, each object has its own copy. We will introduce the definition of copy control members in the next section. However, you have learned all the knowledge needed to define these members. Before continuing to the next section, write the copy constructor and copy assignment operator for HasPtr.

Same as 13.8

13.2.1 class of behavior image value

13.23 compare the copy control member you wrote in the previous exercise with the code in this section. Make sure you understand the difference between your code and ours.

Because I read the whole chapter before writing the exercises, so it's basically the same.

The simple writing method is to judge whether it is self assignment with if. The advanced writing method is realized with swap, which will be shown later

13.24 what happens if the HasPtr version of this section does not define a destructor? What happens if the copy constructor is not defined?

According to the three / five rule, several operations controlled by copy members should be regarded as a whole. Generally, if you want to write them, you can write them all

Undefined destructors can cause memory leaks

Undefined copy constructor will only copy the value of pointer, and several pointers point to the same address

13.25 suppose you want to define the class value version of StrBlob and want to continue using shared_ptr, so that our StrBlobPtr class can still use the weak pointing to the vector_ PTR. Your modified class will need a copy constructor and a copy assignment operator, but no destructor. Explain what copy constructors and copy assignment operators must do. Explain why destructors are not required.

Copy constructor and copy assignment operator need to reallocate memory dynamically

Because the smart pointer used can automatically control the memory release, and the reference count is 0, the object is automatically destroyed, so there is no need for a destructor

13.26 write your own version of the strBlob class described in the previous question.

Add in StrBlob

//copy construction 
StrBlob(const StrBlob& sb):data(std::make_shared<std::vector<std::string>>(*sb.data)){}
//copy assignment 
StrBlob& operator=(const StrBlob& sb){
    data = std::make_shared<std::vector<std::string>>(*sb.data);
    return *this;
}

13.2.2 define classes that behave like pointers

13.27 define your own version of HasPtr using reference counting.

class HasPtr {
public:
	HasPtr(const std::string &s = std::string())
		:ps(new std::string(s)),i(0),use(new std::size_t(1)){}
	HasPtr(const HasPtr &p)
		:ps(p.ps),i(p.i),use(p.use){++*use;}
	HasPtr& operator=(const HasPtr& rhs){
		++*rhs.use;
		if(--*use == 0){
			delete ps;
			delete use;
		}
		ps = rhs.ps;
		i = rhs.i;
		use = rhs.use;
		return *this;
	}
	~HasPtr(){
		if(--*use == 0){
			delete ps;
			delete use;
		}
	}
private:
	std::string *ps;
	int i;
	std::size_t *use;//Reference count
};

13.28 given the following class, implement a default constructor and necessary copy control members for it.

TreeNode has been modified. Count should be a reference count. It should be stored in memory instead of belonging to any object, so it has been changed to type int *

(a) 
class TreeNode {
private:
	std::string value;
	int *count;
	TreeNode *left;
	TreeNode *right;	
};
(b)
class BinStrTree{
private:
	TreeNode *root;	
};
#include <cstddef>
#include <string>


class TreeNode {
public:
	TreeNode()
		:value(std::string()),count(new int(1)),left(nullptr),right(nullptr){}
	TreeNode(const TreeNode& t)
		:value(t.value),count(t.count),left(t.left),right(t.right){
			++*count;
		}
	TreeNode& operator=(const TreeNode& rhs){
		++*rhs.count;
		if(--*count == 0){
			delete left;
			delete right;
			delete count;
		}
		value = rhs.value;
		left = rhs.left;
		right = rhs.right;
		return *this;
	}

	~TreeNode(){
		if(--*count == 0){
			delete left;
			delete right;
			delete count;
		}
	}

private:
	std::string value;
	int* count;
	TreeNode *left;
	TreeNode *right;	
};

class BinStrTree{
public:
	BinStrTree():root(nullptr){}
	BinStrTree(const BinStrTree &bst):root(new TreeNode(*bst.root)){}
	BinStrTree& operator=(const BinStrTree& rhs){
		auto newroot = new TreeNode(*rhs.root);
		delete root;
		root = newroot;
		return *this;
	}
	~BinStrTree(){delete root;}
private:
	TreeNode *root;	
};

13.3 switching operation

13.29 explain that the call to swap in swap (hasptr &, hasptr &) will not cause a recursive loop.

Because no custom swap is written for built-in types, std::swap of the standard library is preferred

13.30 write the swap function for your class valued version of HasPtr and test it. Add a print statement to your swap function to indicate when the function will execute.

#include <string>
#include <iostream>

class HasPtr {
public:
	friend void swap(HasPtr&, HasPtr&);
public:
	HasPtr(const std::string& s = std::string()):
		ps(new std::string(s)), i(0) { }
	HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){}
	HasPtr& operator=(const HasPtr& rhs){
		auto newp = new std::string(*rhs.ps);
		delete ps;
		ps = newp;
		i = rhs.i;
		return *this;
	}

	~HasPtr() {delete ps;} 
private:
	std::string *ps;
	int i;
};

inline 
void swap(HasPtr& lhs, HasPtr& rhs){
	using std::swap;
	swap(lhs.ps, rhs.ps);
	swap(lhs.i,rhs.i);
	std::cout << "using HasPtr's swap" << std::endl;
}

13.31 define a < operator for your HasPtr class and a HasPtr vector. Add some elements to the vector and sort it. Note when swap will be called.

#include <string>
#include <iostream>
#include <vector>
#include <algorithm>

class HasPtr {
public:
	friend void swap(HasPtr&, HasPtr&);
	friend bool operator<(const HasPtr& lhs,const HasPtr& rhs);
public:
	HasPtr(const std::string& s = std::string()):
		ps(new std::string(s)), i(0) { }
	HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){}
	HasPtr& operator=(HasPtr rhs){//The assignment copy should be written in the form of swap, which will be used
		swap(*this,rhs);
		return *this;
	}
	~HasPtr(){delete ps;}

	std::string show(){return *ps;}
	
private:
	std::string *ps;
	int i;
};

inline 
void swap(HasPtr& lhs, HasPtr& rhs){
	using std::swap;
	swap(lhs.ps, rhs.ps);
	swap(lhs.i,rhs.i);
	std::cout << "using HasPtr's swap" << std::endl;
}

inline
bool operator<(const HasPtr& lhs,const HasPtr& rhs){
	return *lhs.ps < *rhs.ps;
}

int main(){
	std::vector<HasPtr>vec;
	HasPtr a("fbc"),b("cde"),c("avpq");
	vec.push_back(a);
	vec.push_back(b);	
	vec.push_back(c);	

	sort(vec.begin(),vec.end());
	for(auto i:vec){
		std::cout <<i.show() << std::endl;
	}

}

Output results

using HasPtr's swap
using HasPtr's swap
using HasPtr's swap
using HasPtr's swap
using HasPtr's swap
avpq
cde
fbc

Will the HasPtr version of 13.32 class pointers benefit from the swap function? If so, what are the benefits? If not, why?

No, the class value version uses its own written swap to avoid reallocation of memory by exchanging pointers. The class pointer version itself is a pointer, so there is no profit

13.4 copy control example

13.33 why are the parameters of save and remove members of Message a Folder &? Why can't we define the parameter as Folder or const Folder?

Because save and remove operate on a specific object, not a class

13.34 write the Message described in this section.

13.36 design and implement the corresponding Folder class. This class should save a set pointing to the Message contained in the Folder.

13.37 add members to the Message class to add and delete a given Folder * to folders. These two members are similar to the addMsg and remMsg operations of the Folder class. (here, the referenced parameters are used instead)

#include <string>
#include <iostream>
#include <set>
#include <vector>

class Folder;
class Message{
public:
	friend void swap(Message &lhs,Message &rhs);
	friend class Folder;
public:
	explicit Message(const std::string &str = ""):contents(str){}
	Message(const Message&);
	Message& operator=(const Message&);
	~Message();

	void save(Folder &);//Add this message to the specified folder
	void remove(Folder &);//Delete this message from the specified folder
private:
	std::string contents;//Save content
	std::set<Folder*>folders;//In which folder s do you want to save this message
	void add_to_Folders(const Message&);
	void remove_from_Folders();
};


class Folder{
public:
	friend void swap(Message &lhs,Message &rhs);
	friend class Message;
public:
	Folder()= default;
	Folder(const Folder &f);
	Folder& operator=(const Folder &f);
	~Folder();

private:
	std::set<Message*>messages;

	void add_to_Message(const Folder&);
    void remove_from_Message();

	void addMsg(const Message *);
	void remMsg(const Message *);
};
void Message::save(Folder &f){
	folders.insert(&f);
	f.addMsg(this);
}

void Message::remove(Folder &f){
	folders.erase(&f);
	f.remMsg(this);
}

void Message::add_to_Folders(const Message &m){
	for(auto f:m.folders)
		f->addMsg(this);
}
Message::Message(const Message &m):contents(m.contents),folders(m.folders){
	add_to_Folders(m);
}

Message::~Message(){
	remove_from_Folders();
}

Message& Message::operator=(const Message &rhs){
	remove_from_Folders();
	contents = rhs.contents;
	folders = rhs.folders;
	add_to_Folders(rhs);
	return *this;
}

void swap(Message &lhs,Message &rhs){
	using std::swap;
	for(auto f:lhs.folders)
		f->remMsg(&lhs);
	for(auto f:rhs.folders)
		f->remMsg(&rhs);
	swap(lhs.folders,rhs.folders);
	swap(lhs.contents,rhs.contents);
	for(auto f:lhs.folders)
		f->addMsg(&lhs);
	for(auto f:rhs.folders)
		f->addMsg(&rhs);

}

void Folder::add_to_Message(const Folder &f) {
    for (auto m : f.messages)
        m->save(*this);
}

Folder::Folder(const Folder &f) : messages(f.messages) { 
    add_to_Message(f); 
}

void Folder::remove_from_Message() {
    for (auto m : messages)
        m->remove(*this);
}

Folder::~Folder() 
{ 
    remove_from_Message(); 
}

Folder &Folder::operator=(const Folder &rhs) {
    remove_from_Message();
    messages = rhs.messages; 
    add_to_Message(rhs);
    return *this;
}

int main(){

	return 0;
}

13.35 what happens if a Message uses a synthetic copy control member?

Synthetic copy control members may cause existing Folders and messages to be out of sync after assignment

13.38 we do not use copy exchange to design the assignment operator of Message. What do you think is the reason?

For the example of dynamically allocating memory, copy exchange is a simple design. The Message class here does not need to dynamically allocate memory, and using copy exchange will only increase the complexity of implementation.

13.5 dynamic memory management class

13.39 write your own version of StrVec, including your own version of reserve, capacity and resize.

Referring to the answer of applenob, I feel that the refactoring and simplification of several codes are well written (such as refining copy_n_move)

Header file

#ifndef StrVec_H
#define StrVec_H

#include <cstdlib>
#include <string>
#include <iostream>
#include <vector>
#include <memory>
#include <utility>
#include <initializer_list>

class StrVec{
public:
	StrVec():elements(nullptr),first_free(nullptr),cap(nullptr){}
	StrVec(const StrVec&);
	StrVec& operator=(const StrVec&);
	~StrVec();

public:
	void push_back(const std::string&);
	size_t size()const{return first_free-elements;}
	size_t capacity()const{return cap-elements;}
	std::string* begin()const{return elements;}
	std::string* end()const{return first_free;}

	void reserve(size_t new_cap);
	void resize(size_t count);
	void resize(size_t count,const std::string& s);
private:
	static std::allocator<std::string>alloc;
	void chk_n_alloc(){
		if (size() == capacity())reallocate();
	}
	std::pair<std::string*,std::string*>alloc_n_copy(const std::string*,const std::string*);
	void free();
	void reallocate();
	void alloc_n_move(size_t new_cap);
private:
	std::string *elements;
	std::string *first_free;
	std::string *cap;
};

#endif

Implementation file

#include "StrVec.hpp"
#include <memory>
#include <string>

void StrVec::push_back(const std::string& s){
	chk_n_alloc();
	alloc.construct(first_free++,s);
}

std::pair<std::string*,std::string*>
StrVec::alloc_n_copy (const std::string*b,const std::string*e){
	auto data = alloc.allocate(e-b);
	return {data,std::uninitialized_copy(b, e,data)};
}

void StrVec::free(){
	if(elements){
		for(auto p = first_free; p != elements;)
			alloc.destroy(--p);
		alloc.deallocate(elements,cap-elements);
	}
}

StrVec::StrVec(const StrVec &s){
	auto newdata = alloc_n_copy(s.begin(), s.end());
	elements = newdata.first;
	first_free = cap = newdata.second;
}

StrVec::~StrVec(){free();}

StrVec& StrVec::operator=(const StrVec &rhs){
	auto data = alloc_n_copy(rhs.begin(),rhs.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}

void StrVec::alloc_n_move (size_t new_cap){
	auto newdata = alloc.allocate(new_cap);
	auto dest = newdata;
	auto elem = elements;
	for(size_t i = 0 ;i != size();++i)
		alloc.construct(dest++,std::move(*elem++));
	free();
	elements = newdata;
	first_free = dest;
	cap = elements + new_cap;
}
void StrVec::reallocate (){
	auto newcapacity = size()?2*size():1;
	alloc_n_move(newcapacity);
}

void StrVec::reserve(size_t new_cap){
	if(new_cap <= capacity())return;
	alloc_n_move(new_cap);
}

void StrVec::resize(size_t count){
	resize(count,std::string());
}

void StrVec::resize(size_t count,const std::string& s){
	if(count > size()){
		if(count > capacity())reserve(count*2);
		for(size_t i = size(); i != count;++i){
			alloc.construct(first_free++,s);
		}
	}else if(count <size()){
		while(first_free != elements + count)
			alloc.destroy(--first_free);
	}
}


13.40 add a constructor for your StrVec class, which accepts an initializer_ List < string > parameter.

#ifndef StrVec_H
#define StrVec_H

#include <cstdlib>
#include <string>
#include <iostream>
#include <vector>
#include <memory>
#include <utility>
#include <initializer_list>

class StrVec{
public:
	StrVec():elements(nullptr),first_free(nullptr),cap(nullptr){}
	StrVec(const StrVec&);
	StrVec& operator=(const StrVec&);
	~StrVec();
	StrVec(std::initializer_list<std::string>);

public:
	void push_back(const std::string&);
	size_t size()const{return first_free-elements;}
	size_t capacity()const{return cap-elements;}
	std::string* begin()const{return elements;}
	std::string* end()const{return first_free;}

	void reserve(size_t new_cap);
	void resize(size_t count);
	void resize(size_t count,const std::string& s);
private:
	static std::allocator<std::string>alloc;
	void chk_n_alloc(){
		if (size() == capacity())reallocate();
	}
	std::pair<std::string*,std::string*>alloc_n_copy(const std::string*,const std::string*);
	void free();
	void reallocate();
	void alloc_n_move(size_t new_cap);
	void range_initialize(const std::string *first, const std::string *last);

private:
	std::string *elements;
	std::string *first_free;
	std::string *cap;
};

#endif
#include "StrVec.hpp"
#include <memory>
#include <string>

void StrVec::push_back(const std::string& s){
	chk_n_alloc();
	alloc.construct(first_free++,s);
}

std::pair<std::string*,std::string*>
StrVec::alloc_n_copy (const std::string*b,const std::string*e){
	auto data = alloc.allocate(e-b);
	return {data,std::uninitialized_copy(b, e,data)};
}

void StrVec::free(){
	if(elements){
		for(auto p = first_free; p != elements;)
			alloc.destroy(--p);
		alloc.deallocate(elements,cap-elements);
	}
}

StrVec::StrVec(const StrVec &s){
	auto newdata = alloc_n_copy(s.begin(), s.end());
	elements = newdata.first;
	first_free = cap = newdata.second;
}

StrVec::~StrVec(){free();}

StrVec& StrVec::operator=(const StrVec &rhs){
	auto data = alloc_n_copy(rhs.begin(),rhs.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}

void StrVec::range_initialize(const std::string *first, const std::string *last){
    auto newdata = alloc_n_copy(first, last);
    elements = newdata.first;
    first_free = cap = newdata.second;
}

StrVec::StrVec(std::initializer_list<std::string> il)
{
    range_initialize(il.begin(), il.end());
}

void StrVec::alloc_n_move (size_t new_cap){
	auto newdata = alloc.allocate(new_cap);
	auto dest = newdata;
	auto elem = elements;
	for(size_t i = 0 ;i != size();++i)
		alloc.construct(dest++,std::move(*elem++));
	free();
	elements = newdata;
	first_free = dest;
	cap = elements + new_cap;
}
void StrVec::reallocate (){
	auto newcapacity = size()?2*size():1;
	alloc_n_move(newcapacity);
}

void StrVec::reserve(size_t new_cap){
	if(new_cap <= capacity())return;
	alloc_n_move(new_cap);
}

void StrVec::resize(size_t count){
	resize(count,std::string());
}

void StrVec::resize(size_t count,const std::string& s){
	if(count > size()){
		if(count > capacity())reserve(count*2);
		for(size_t i = size(); i != count;++i){
			alloc.construct(first_free++,s);
		}
	}else if(count <size()){
		while(first_free != elements + count)
			alloc.destroy(--first_free);
	}
}

13.41 push_ In back, why do we use post increment operation in construct call? What happens if you use a pre increment operation?

An empty location will be skipped

13.42 test your StrVec class by replacing vector < string > with your StrVec class in your TextQuery and QueryResult classes.

13.43 rewrite the free member with for_ Replace the for loop destroy element with each and lambda. Which implementation do you prefer and why?

void StrVec::free(){
	if(elements){
        for_each(elements,first_free,[this](std::string& s){alloc.destroy(&s);})
		alloc.deallocate(elements,cap-elements);
	}
}

for_each+lambda is more concise and can avoid crossing boundaries

13.44 write a simplified version of the standard library String class, named String. Your class should have at least one default constructor and one constructor that accepts C-style String pointer parameters. Use allocator to allocate the required memory for your String class.

StrVec and applenon in reference books

MyString.hpp

#ifndef MyString_H
#define MyString_H

#include <memory>
#include <algorithm>
#include <iterator>
class MyString{
public:
	MyString():MyString(""){}
	MyString(const char*);
	MyString(const MyString&);
	MyString& operator=(const MyString&);
	~MyString();
public:
	size_t size()const{return ends-elements;}
	const char* begin()const{return elements;}
	const char* end()const{return ends;}

private:
	std::allocator<char> alloc;
	std::pair<char*,char*>alloc_n_copy(const char*,const char*);
	void free();
	void range_initializer(const char *first, const char *last);

private:
	char* elements;
	char* ends;
};


#endif

MyString.cpp

#include "MyString.hpp"

std::pair<char*,char*>
MyString::alloc_n_copy(const char*b,const char*e){
	auto data = alloc.allocate(e-b);
	return {data,std::uninitialized_copy(b, e,data)};
}

void MyString::free(){
	if(elements){
		std::for_each(elements,ends,[this](char &c){alloc.destroy(&c);});
		alloc.deallocate(elements,ends-elements);
	}
}

void MyString::range_initializer(const char *first, const char *last){
    auto newstr = alloc_n_copy(first, last);
    elements = newstr.first;
    ends = newstr.second;
}
MyString::MyString(const char* s){
	char * sl = const_cast<char*>(s);
	while(*sl)
		++sl;
	range_initializer(s,++sl);
}

MyString::~MyString(){free();}

MyString& MyString::operator=(const MyString &rhs){
	auto data = alloc_n_copy(rhs.begin(),rhs.end());
	free();
	elements = data.first;
	ends =  data.second;
	std::cout << "call MyString& MyString::operator=(const MyString &rhs)" << std::endl;
	return *this;
}

MyString::MyString(const MyString& ms){
	range_initializer(ms.begin(),ms.end());
	std::cout << "MyString::MyString(const MyString& ms)" << std::endl;
}

Test.cpp

#include <memory>
#include <vector>
#include <iostream>
#include "MyString.hpp"

void foo(MyString x)
{
    std::cout << x.begin() << std::endl;
}

void bar(const MyString& x)
{
    std::cout << x.begin() << std::endl;
}

MyString baz()
{
    MyString ret("world");
    return ret;
}

int main()
{
    char text[] = "world";

    MyString s0;
    MyString s1("hello");
    MyString s2(s0);
    MyString s3 = s1;
    MyString s4(text);
    s2 = s1;

    foo(s1);
    bar(s1);
    foo("temporary");
    bar("temporary");
    MyString s5 = baz();

    std::vector<MyString> svec;
    svec.reserve(8);
    svec.push_back(s0);
    svec.push_back(s1);
    svec.push_back(s2);
    svec.push_back(s3);
    svec.push_back(s4);
    svec.push_back(s5);
    svec.push_back(baz());
    svec.push_back("good job");

    for (const auto &s : svec) {
        std::cout << s.begin() << std::endl;
    }
}

makefile

main:Test.o MyString.o
	clang++ Test.o MyString.o -o main -g
Test.o:Test.cpp
	clang++ -c Test.cpp -o Test.o
MyString.o:MyString.cpp MyString.hpp
	clang++ -c MyString.cpp -o MyString.o
clean:
	rm *.o main

13.6 object movement

13.6.1 right value reference

13.45 explain the difference between lvalue reference and lvalue reference?

Lvalue reference: general reference

R-value reference: a reference bound to an R-value

  • The right value reference must be bound to the right value (the expression requiring conversion, literal constant, and the expression returning the right value). It cannot be directly bound to an left value. It can be obtained by & & instead of &
    • Functions that return lvalue references, such as connected assignment, subscript, dereference and pre increment / decrement operators, are all examples of lvalue expressions
    • Functions that return non reference types, connected arithmetic, relation, bit and post value increment / decrement operators, all generate right values. You cannot bind an lvalue reference to such an expression, but you can bind a cosnt lvalue reference or an rvalue reference to such an expression
  • The right value reference can only be bound to an object to be destroyed (just like playing a game, there are weapons on the opposite side, and you can pick up weapons only when the opposite side is dead)
  • Lvalue persistence: the identity of an object with a persistent state
  • R-value is short: the value of the object is either a literal constant or a temporary object created during the evaluation of the expression

13.46 what types of references can be bound to the following initializers?

int f();
vector<int> vi(100);
int? r1 = f();//R-value reference, return function of non reference type
int? r2 = vi[0];//Lvalue reference, subscript
int? r3 = r1;//Lvalue reference, variable is lvalue, even if the variable is lvalue reference (lvalue reference is not equal to lvalue).
int? r4 = vi[0] * f();//R-value reference, which returns the expression of R-value (R-value is temporary)

13.47 for the String class you defined in exercise 13.44, add a statement for its copy constructor and copy assignment operator, and print a message each time the function executes.

13.48 define a vector < String > and call push multiple times on it_ back. Run your program and observe how many times the String has been copied.

See exercise 13.44

13.6.2 move constructor and move assignment operator

13.49 add a move constructor and a move assignment operator to your StrVec, String and Message classes.

Take String as an example

#ifndef MyString_H
#define MyString_H

#include <memory>
#include <algorithm>
#include <iterator>
#include <iostream>

class MyString{
public:
	MyString():MyString(""){}
	MyString(const char*);
	MyString(const MyString&);
	MyString& operator=(const MyString&);
	MyString(MyString &&) noexcept;//move constructor 
	MyString& operator=(MyString &&)noexcept;//Move assignment function
	~MyString();
public:
	size_t size()const{return ends-elements;}
	const char* begin()const{return elements;}
	const char* end()const{return ends;}

private:
	std::allocator<char> alloc;
	std::pair<char*,char*>alloc_n_copy(const char*,const char*);
	void free();
	void range_initializer(const char *first, const char *last);

private:
	char* elements;
	char* ends;
};


#endif

MyString.cpp

#include "MyString.hpp"

std::pair<char*,char*>
MyString::alloc_n_copy(const char*b,const char*e){
	auto data = alloc.allocate(e-b);
	return {data,std::uninitialized_copy(b, e,data)};
}

void MyString::free(){
	if(elements){
		std::for_each(elements,ends,[this](char &c){alloc.destroy(&c);});
		alloc.deallocate(elements,ends-elements);
	}
}

void MyString::range_initializer(const char *first, const char *last){
    auto newstr = alloc_n_copy(first, last);
    elements = newstr.first;
    ends = newstr.second;
	std::cout << "void MyString::range_initializer(const char *first, const char *last)" << std::endl;
}
MyString::MyString(const char* s){
	char * sl = const_cast<char*>(s);
	while(*sl)
		++sl;
	range_initializer(s,++sl);
}

MyString::~MyString(){free();}

MyString& MyString::operator=(const MyString &rhs){
	auto data = alloc_n_copy(rhs.begin(),rhs.end());
	free();
	elements = data.first;
	ends =  data.second;
	std::cout << "call MyString& MyString::operator=(const MyString &rhs)" << std::endl;
	return *this;
}

MyString::MyString(const MyString& ms){
	range_initializer(ms.begin(),ms.end());
	std::cout << "MyString::MyString(const MyString& ms)" << std::endl;
}
MyString::MyString(MyString && ms) noexcept:elements(ms.elements),ends(ms.ends){
	std::cout << "MyString::MyString(MyString && ms)" << std::endl;
	ms.elements = ms.ends = nullptr;
}

MyString& MyString::operator=(MyString && ms)noexcept{
	if(this != &ms){
		free();
		elements = ms.elements;
		ends = ms.ends;
		ms.elements = ms.ends = nullptr;
	}
	std::cout << "MyString& MyString::operator=(MyString && ms)" << std::endl;
	return *this;
}




Test.cpp

#include <memory>
#include <vector>
#include <iostream>
#include "MyString.hpp"

void foo(MyString x)
{
    std::cout << x.begin() << std::endl;
}

void bar(const MyString& x)
{
    std::cout << x.begin() << std::endl;
}

MyString baz()
{
    MyString ret("world");
    return ret;
}

int main(){
//Reference https://blog.csdn.net/qq_24739717/article/details/104473034#t48
    char text[] = "world";
	std::cout << "---------------definition-----------------" << std::endl;
    MyString s0;
    MyString s1("hello");
    MyString s2(s0);
    MyString s3 = s1;
    MyString s4(text);
    s2 = s1;

	std::cout << "---------------call-----------------" << std::endl;
    foo(s1);
    bar(s1);
    foo("temporary");
    bar("temporary");
    MyString s5 = baz();
	std::cout << "---------------add to-----------------" << std::endl;
    std::vector<MyString> svec;
    svec.reserve(8);
    svec.push_back(s0);
    svec.push_back(s1);
    svec.push_back(s2);
    svec.push_back(s3);
    svec.push_back(s4);
    svec.push_back(s5);
    svec.push_back(baz());//Avoid copying
    svec.push_back("good job");//Avoid copying
	std::cout << "---------------output-----------------" << std::endl;
    for (const auto &s : svec) {
        std::cout << s.begin() << std::endl;
    }
}

13.50 add a print statement to the move operation of your String class, and rerun the program in exercise 13.48 in section 13.6.1. It uses a vector < String > to observe when copying will be avoided.

---------------definition-----------------
void MyString::range_initializer(const char *first, const char *last)
void MyString::range_initializer(const char *first, const char *last)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
call MyString& MyString::operator=(const MyString &rhs)
---------------call-----------------
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
hello
hello
void MyString::range_initializer(const char *first, const char *last)
temporary
void MyString::range_initializer(const char *first, const char *last)
temporary
void MyString::range_initializer(const char *first, const char *last)
---------------add to-----------------
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(MyString && ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(MyString && ms)
---------------output-----------------

hello
hello
hello
world
world
world
good job

13.51 although unique_ptr cannot be copied, but we wrote a clone function in section 12.1.5, which returns a unique value_ ptr. Explain why the function is legal and why it works correctly.

We can copy or assign a unique to be destroyed_ The most common example of PTR is to return a unique from a function_ ptr

In fact, the move constructor or move assignment operator is called to take over the unique function in this function_ Ownership of PTR

13.52 explain in detail what happened to the assignment of HasPtr object on page 478? In particular, step by step, describe what changes have taken place in the value of the parameter rhs in the assignment operators of hp, hp2 and HasPtr.

Left value copy, right value move

hp = hp2;

  • hp2 is an lvalue, so the parameter is copied through the copy constructor
  • rhs is a copy of hp2. They are independent and have the same string content. After the assignment, the rhs is destroyed

hp = std::move(hp2);

  • std::move binds an R-value reference to hp2 and uses the move constructor
  • rhs takes over ownership of hp2

Regardless of whether a copy construct or a move construct is used, the function body of the assignment operator will swap the states of the two operands. After exchanging the two object pointers, the pointer in rhs points to the string used by the original left operands. When rhs leaves the scope, this string will be destroyed

13.53 from the perspective of bottom efficiency, the assignment operator of HasPtr is not ideal. Why? Implement a copy assignment operator and a move assignment operator for HasPtr, and compare the operations performed in your new move assignment operator with those performed in the copy and exchange version.

Compile related parameters

clang++ -Wall -std=c++14 -O2  -fsanitize=undefined,address
#include <string>
#include <iostream>
#include <ctime>

class HasPtr {
public:
	friend void swap(HasPtr&, HasPtr&);
public:
	HasPtr(const std::string& s = std::string()):ps(new std::string(s)), i(0) { }//Default construction
	HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){}//copy construction 
	HasPtr& operator=(HasPtr rhs){//Copy assignment swap version
		swap(*this,rhs);
		return *this;
	}
	HasPtr(HasPtr && hp)noexcept:ps(hp.ps),i(hp.i){//Mobile structure
		hp.ps = nullptr;
	}

	~HasPtr() {delete ps;} 
private:
	std::string *ps;
	int i;
};

inline 
void swap(HasPtr& lhs, HasPtr& rhs){
	using std::swap;
	swap(lhs.ps, rhs.ps);
	swap(lhs.i,rhs.i);
}

int main(){
	HasPtr hp1("test1"),hp2("test2");
	clock_t start = clock();
	hp1 = hp2;
	clock_t end = clock();
	std::cout << end - start << std::endl;
	return 0;
}
#include <string>
#include <iostream>
#include <ctime>

class HasPtr {
public:
	friend void swap(HasPtr&, HasPtr&);
public:
	HasPtr(const std::string& s = std::string()):ps(new std::string(s)), i(0) { }//Default construction
	HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){}//copy construction 
	HasPtr(HasPtr && hp)noexcept:ps(hp.ps),i(hp.i){//Mobile structure
		hp.ps = nullptr;
	}
	HasPtr& operator=(const HasPtr& rhs){//copy assignment 
		auto newp = new std::string(*rhs.ps);
		delete ps;
		ps = newp;
		i = rhs.i;
		return *this;
	}
	HasPtr& operator=(HasPtr&& rhs)noexcept{
		if(this != &rhs){
			delete ps;
			ps = rhs.ps;
			rhs.ps = nullptr;
		}
		return *this;
	}

	~HasPtr() {delete ps;} 
private:
	std::string *ps;
	int i;
};

inline 
void swap(HasPtr& lhs, HasPtr& rhs){
	using std::swap;
	swap(lhs.ps, rhs.ps);
	swap(lhs.i,rhs.i);
}

int main(){
	HasPtr hp1("test1"),hp2("test2");
	clock_t start = clock();
	hp1 = hp2;
	clock_t end = clock();
	std::cout << end - start << std::endl;
	return 0;
}

It seems that the gap is not big, and even the swap is faster? There may be a problem with the measurement / writing

Referring to the explanation of other predecessors, the swap operation will create a temporary space to store the value of the temporary variable, so the assignment operation efficiency is low

13.54 what happens if we define the move assignment operator for HasPtr, but do not change the copy and exchange operator? Write code to verify your answer.

#include <string>
#include <iostream>
#include <ctime>

class HasPtr {
public:
	friend void swap(HasPtr&, HasPtr&);
public:
	HasPtr(const std::string& s = std::string()):ps(new std::string(s)), i(0) { }//Default construction
	HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){}//copy construction 
	HasPtr(HasPtr && hp)noexcept:ps(hp.ps),i(hp.i){//Mobile structure
		hp.ps = nullptr;
	}
	HasPtr& operator=(HasPtr rhs){//Copy assignment swap version
		swap(*this,rhs);
		return *this;
	}
	HasPtr& operator=(HasPtr&& rhs)noexcept{
		if(this != &rhs){
			delete ps;
			ps = rhs.ps;
			rhs.ps = nullptr;
		}
		return *this;
	}

	~HasPtr() {delete ps;} 
private:
	std::string *ps;
	int i;
};

inline 
void swap(HasPtr& lhs, HasPtr& rhs){
	using std::swap;
	swap(lhs.ps, rhs.ps);
	swap(lhs.i,rhs.i);
}

int main(){
    HasPtr hp1("test"),*hp2 = new HasPtr("train");
    hp1 = std::move(*hp2);
	return 0;
}

report errors

error: use of overloaded operator '=' is ambiguous (with operand types 'HasPtr' and 'typename std::remove_reference<HasPtr &>::type' (aka 'HasPtr'))

13.6.3 right value reference and member function

13.55 add an R-value reference version of push for your StrBlob_ back.

void push_back(string &&t){data->push_back(std::move(t));}

13.56 what happens if sorted is defined as follows?

Foo Foo::sorted() const & {
	Foo ret(*this);
	return ret.sorted();
}

ret is generated by the copy constructor. It is an lvalue and called circularly

13.57 what happens if sorted is defined as follows:

Foo Foo::sorted() const & { return Foo(*this).sorted(); }

Foo(*this) is a temporary quantity, which is an R-value. It will call sorted() of the R-value version

13.58 write a new version of Foo class. There are printed statements in its sorted function. Test this class to verify whether your answers to the first two questions are correct.

#include <algorithm>
#include <string>
#include <vector>
#include <iostream>

class Foo{
public:
	Foo sorted() &&;
	Foo sorted() const &;
private:
	std::vector<int> data;
};

Foo Foo::sorted()&&{
	sort(data.begin(),data.end());
	std::cout << "Foo Foo::sorted()&&" << std::endl;
	return *this;
}

Foo Foo::sorted() const &{
	Foo ret(*this);
	sort(ret.data.begin(),ret.data.end());
	std::cout << "Foo Foo:sorted() const &" << std::endl;
	return ret;
}

int main(){
	Foo().sorted();//Call the right value version. If there is no written right value version, the left value version will be called
	Foo f;
	f.sorted();//Call lvalue version
	return 0;
}

output

Foo Foo::sorted()&&
Foo Foo:sorted() const &

Topics: C++ Back-end