0
点赞
收藏
分享

微信扫一扫

C++ primer 第十三章复习 13.1

JakietYu 2022-01-21 阅读 108

C++ primer 第十三章复习

13.1 拷贝,赋值与销毁

类 有五种特殊成员函数控制对象拷贝,移动,赋值和销毁

  1. 拷贝构造函数
  2. 拷贝赋值运算符
  3. 移动构造函数
  4. 移动赋值运算符
  5. 析构函数

上述操作称为拷贝控制操作,若一个类没有定义这些函数,编译器会自动生成缺失的函数

拷贝构造函数

拷贝构造函数通常不应该是 explicit ( explicit 不可以隐式初始化对象)

class Foo{
public:
	Foo(){} //构造函数
	
	Foo(const Foo&){} //拷贝构造函数
};

在C++中若构造函数只有一个参数, 那么在编译时会有一个缺省的转换:将该构造函数对应数据类型数据转换为该类对象,例如

CxString string2 = 10

//转换为
CxString string2(10);  
或  
CxString temp(10);  
CxString string2 = temp;
#include <iostream>
using namespace std;
class Test{
public:
	Test(){}
	//拷贝构造函数
	Test(const Test &t){
		cout << "in copy" << endl;
		data = t.data;
	}

	Test& Test::operator=(const Test &t){
		std::cout << "in =" << std::endl;
		return *this;
	}

private:
	int data;
};

int main(){
	Test t1;
	Test t2(t1);//由于是显式调用拷贝构造函数,所以编译过

	Test t3;
	t3 = t2;

	Test t4 = t2;//由于是隐式调用拷贝构造函数,所以编译不过
	return 0;
}

//没有初始化对象无法调用 = 操作符函数(),所以如果要走 = 操作符函数,那么需要 Test t4; t4 = t2; 两步.故 Test t4 = t2 会被编译器优化为 Test t4(t2)

合成拷贝构造函数

涉及到其它类的拷贝,拷贝构造函数内部会调用其它类的拷贝构造

class Sales_data{
	public:
		Sales_data(const Sales_data&);
	
	private:
		std::string bookNo;
		int units_sold = 0;
};

Sales_data::Sales_data(const Sales_data& orig):
		bookNo(orig.bookNo),		//调用string的拷贝构造函数
		units_sold(orig.units_sold){}

拷贝初始化

拷贝构造函数用来初始化非引用类型参数,所以自己的参数必须是引用(如果不是引用,又会调用参数的拷贝构造)

std::string dots(10, '.'); //构造函数初始化
std::string str1(dots);	//显式拷贝构造函数初始化
std::string str2 = dots; //隐式拷贝构造函数初始化
std::string str3 = "9-99-999"; //构造函数初始化
std::string str4 = std::string(100, '9');//显式构造函数 + 隐式拷贝构造函数初始化

拷贝初始化的限制

explicit 单参构造函数,无法使用 = 成员值进行拷贝构造,作为函数参数同理

explicit vector(size_type n); //vec的构造函数
vector (const vector& x); //vec的拷贝构造函数

std::vector<int> v1(10); //正确,构造函数初始化
std::vector<int> v2 = 10; //错误,构造函数是 explicit 的

void f(std::vector<int>);
f(10);	//错误,构造函数是 explicit 的
f(std::vector<int>(10)); //正确, std::vector<int> tmp =  std::vector<int>(10);

编译绕过拷贝构造函数

std::string str3 = "9-99-999";
//改写为
std::string str3("9-99-999");
//直接走构造函数初始化,而不会走隐式构造函数 + 隐式拷贝构造函数初始化

拷贝赋值运算符

拷贝赋值运算符接受一个调用类相同类型的参数

class Foo{
public:
	Foo(){}
	Foo(const Foo&){}

	//拷贝赋值运算符接受一个调用类相同类型的参数
	Foo& operator==(const Foo&){}
};

int main(){
    Foo f2;
    
    Foo f1;
    f1 = f2;
}

合成拷贝赋值运算符

涉及到其它类的拷贝运算符,拷贝运算符会调用其它类的拷贝运算符

class Sales_data{
	public:
		Sales_data(const Sales_data&);
		Sales_data& operator=(const Sales_data&);
	private:
		std::string bookNo;
		int units_sold = 0;
};

Sales_data& Sales_data::operator=(const Sales_data&){
    bookNo = orig.bookNo;
    units_sold = orig.units_sold; //调用 string 的拷贝运算符
    return *this;
}

析构函数

不接受参数,不允许重载

构造函数初始化对象的非 static 数据成员 (先初始化成员再构造函数 父 -> 子)

析构函数释放对象成员,并销毁非 static 数据成员(先调用析构函数,再释放成员 子 -> 父)

先调用 A 析构,再销毁 A 数据成员 ,最后销毁父类 B,和创建刚好相反

class Foo{
public:
	~Foo(){}
};

1、成员初始化顺序:只与类成员的声明顺序有关

2、初始化列表与构造函数体内初始化的区别:(内置数据类型除外)

在成员初始化列表中初始化,和在构造函数体内赋值,内置数据类型,复合类型(指针,引用)性能和结果完全一样,但用户定义类型(类类型)结果相同,但性能上存在差别。因类类型的数据成员对象在进入函数体前已构造完成,在成员初始化列表处进行构造对象工作,若在构造函数体内赋值相当于先进行构造函数再调用拷贝运算符,而成员初始化列表只调用拷贝构造函数即可

合成析构函数

在类的析构函数结束后,会自动调用成员的析构函数

class Foo{
public:
    //成员会自动销毁,不需要额外动作
	~Foo(){} 
	
private:
	std::string data;
};

需要析构函数的类也需要拷贝和赋值操作

析构释放指针的类,需要拷贝函数和赋值操作;需要拷贝操作的类,也需要赋值操作,反之亦然

对一个类的每个对象分配一个唯一的 ID :需要自定义拷贝构造函数和赋值运算符,但不需要自定义析构函数

class HashPtr{
public:
	//构造函数
	HashPtr(const std::string& s = std::string()) :
		data(new std::string(s)), num(0){}

	//编译器生成的拷贝函数
	HashPtr(const HashPtr& hashPtr){
		(*this).data = hashPtr.data;
		this->num = hashPtr.num;
	}

	//编译器生成的拷贝操作符
	const HashPtr& operator=(const HashPtr& hashPtr){
		(*this).data = hashPtr.data;
		this->num = hashPtr.num;
		return *this;
	}

	//析构函数
	~HashPtr(){ delete data; }

private:
	std::string* data;
	int num;
};

HashPtr test(HashPtr hashPtr){ //调用编译器生成的拷贝构造 HashPtr hashPtr(p1)
	HashPtr ret = hashPtr; //调用编译器生成的拷贝操作符
	return ret; // hashPtr 对象和 ret 将被销毁
}

int main(){
	HashPtr p1("some values");
	test(p1); //调用结束后, p1.data 指向的内存被释放了

	HashPtr p2(p1); //现在 p1,p2 的 data 变成了野指针

	system("pause");
	return 0;
}

显式缺省函数

(=default) , default只能用于6个特殊成员函数,但delete可用于任何成员函数

=default 可以显式的要求编译器生成合成的版本

class Sales_data{
	public:
		Sales_data() = default;
		Sales_data(const Sales_data&) = default;
		Sales_data& operator=(const Sales_data&);
		~Sales_data() = default;

	private:
		std::string m_data;
};

// = default 生成的赋值运算符函数返回的对象类型是 Sales_data& 非 const
Sales_data& Sales_data::operator=(const Sales_data&) = default;

阻止拷贝

例,IOStream 类阻止拷贝,以免对个对象写入或读取相同的 IO 缓存

struct NoCopy
{
	NoCopy() = default;
	//通知编译器,不希望定义这些成员函数
	NoCopy(const NoCopy&) = delete; //阻止拷贝
	NoCopy& operator=(const NoCopy&) = delete;
	~NoCopy() = default;
};

struct NoDtor
{
	NoDtor() = default;//默认构造
	~NoDtor() = delete;//阻止析构
};

对于析构已删除的类,不能定义该类的对象或释放该类指针

struct NoDtor
{
	NoDtor() = default;//默认构造
	~NoDtor() = delete;//阻止析构
};

int main(){
	//NoDtor nd; //报错,编译检测,因为对象不能正常销毁
	
	NoDtor* p = new NoDtor();
	delete p; //报错

	system("pause");
	return 0;
}

本质上,当不能 拷贝,赋值,删除 类成员时,类的合成拷贝函数已被定义为删除的

C++11 1前,阻止拷贝是通过 private 实现的,但是友元和成员函数仍可以拷贝

struct PrivateCopy
{
private:
	PrivateCopy(const PrivateCopy&);
	PrivateCopy& operator=(const PrivateCopy&);

public:
	//默认的合成构造函数,可使用类对象无法拷贝
	PrivateCopy() = default;
	~PrivateCopy() = delete;
};

单例模式

实现 = delete 实现单例

template<typename T>
class Singleton
{
public:
    static T& GetInstance()
    {
        static T instance;
        return instance;
    }
 
    Singleton(T&&) = delete;
    Singleton(const T&) = delete;
    void operator= (const T&) = delete;
 
protected:
    Singleton() = default;
    virtual ~Singleton() = default;
};
举报

相关推荐

0 条评论