#include<iostream>
#include<string>
#include<vector>
#include<functional>
#include<algorithm>
#include<map>
#include<queue>
using namespace std;
//一个类通常定义五中特殊的成员函数来控制这些操作,包括拷贝构造函数,拷贝赋值运算符,移动构造函数,移动赋值运算符和析构函数
//我们称这些操作为拷贝控制操作
//拷贝,赋值和销毁
class Foo {
public:
Foo();
Foo(const Foo&); //拷贝构造函数的第一个参数必须是引用类型,拷贝构造函数在几种情况下都会被隐式的使用,因此,拷贝构造函数
//通常不应该是explicit
};
//拷贝初始化
void test01() {
string dots(10, '.'); //直接初始化
string s(dots); // 直接初始化
string s2 = dots; //拷贝初始化
string null_book = "9-99999"; //拷贝初始化
string null_book2("9999"); //编译器绕过了拷贝构造函数
string nines = string(100, '9'); //拷贝初始化
/*
当我们使用直接初始化时,我们实际上是要求编译器使用普通的函数匹配,来选择与我们提供的参数最匹配的构造函数
当我们使用拷贝初始化,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转化
*/
/*
拷贝初始化不仅在我们用=定义变量时会发生
在以下情况下也会发生
1.将一个对象作为实参传递给一个非引用类型的形参
2.从一个返回类型为非引用类型的函数返回一个对象
3.用花括号初始化一个数组中的元素或一个聚合类中的成员
*/
//拷贝初始化的限制
vector<int>v1(10); //正确,直接初始化
vector<int>v2 = 10; //错误,接受大小参数的构造函数是explicit的
void f(vector<int>);
f(10); //错误,不能用一个explicit的构造函数拷贝一个实参
f(vector<int>(10)); //正确,从一个int直接构造一个临时vector
}
//拷贝赋值运算符
//重载赋值运算符
class Foo {
public:
Foo& operator=(const Foo&); //赋值运算符通常应该返回一个指向其左侧运算对象的引用
};
//析构函数
class Foo {
public:
~Foo(); //通常析构函数释放对象在生存期分配的所有资源
//隐式销毁一个内置指针类型的成员不会delete它所指向的对象
//与普通指针不同,智能指针是类类型,所以具有析构函数,因此与普通普通指针不同,智能指针成员在析构阶段会被自动销毁
/*
无论何时一个对象被销毁,就会自动调用其析构函数
1.变量在离开其作用越时被销毁
2.当一个对象被销毁时,其成员被销毁
3.容器被销毁时,其元素被销毁
4.对于动态分配的对象,当对指向他的指针应用delete运算符时被销毁
5.对于临时对象,当创建他的完整表达式结束时被销毁
*/
};
//三五法则
//需要析构函数的类也需要拷贝和赋值操作
//需要尅被操作的类也需要赋值操作,反之亦然
//使用=default
class Sales_data {
public:
Sales_data() = default;
Sales_data(const Sales_data&) = default;
Sales_data& operator=(const Sales_data&);
~Sales_data() = default;
//我们只能对具有合成版本的成员函数使用=default(即默认构造函数或拷贝控制成员)
};
//阻止拷贝
/*
iostream类阻止了拷贝,以避免多个对象写入或读取相同的IO缓冲
*/
struct NoCopy {
NoCopy() = default;
NoCopy(const NoCopy&) = delete; //阻止拷贝
NoCopy& operator = (const NoCopy&) = delete; //阻止拷贝
~NoCopy() = default;
/*
=default与 =delete的区别
1.与=default不同,=delete必须出现在函数第一次声明的时候,
2.我们可以对任何函数指定=delete
*/
//析构函数不能是删除的成员,如果析构函数被删除,就无法销毁此类型的对象了
};
//private拷贝控制
class PrivateCopy {
PrivateCopy(const PrivateCopy&);
PrivateCopy& operator=(const PrivateCopy&);
public:
PrivateCopy() = default;
~PrivateCopy();
/*
由于析构函数是public的,用户可以定义PrivateCopy类型的对象,但是,由于
拷贝构造函数和拷贝赋值运算符时private的,用户代码将不能拷贝这个类型的对象
但是友元和成员函数任然可以拷贝对象
为了阻止友元和成员函数进行拷贝,我们将这些拷贝控制成员声明为private的,但并不定义他们
*/
};
//希望阻止拷贝的类应该使用=delete来定义他们自己的拷贝构造函数和拷贝赋值运算符,而不应该将他们声明为private的
//拷贝控制和资源管理
//行为像值的类
class HasPtr {
public:
HasPtr(const std::string& s = std::string()) :
ps(new std::string(s)), i(0) {}
HasPtr(const HasPtr& p) :
ps(new std::string(*p.ps)), i(p.i) {}
HasPtr& operator=(const HasPtr&);
private:
std::string* ps;
int i;
};
//类值拷贝赋值运算符
//赋值运算符通常组合了析构函数和构造函数的操作
HasPtr& HasPtr::operator=(const HasPtr& rhs) {
auto newp = new string(*rhs.ps);
delete ps; //释放旧内存
ps = newp; //从右侧运算对象拷贝数据到本对象
i = rhs.i;
return *this;
}
//这样编写赋值运算符时错误的
HasPtr& HasPtr::operator=(const HasPtr& rhs) {
delete ps;
ps = new string(*(rhs.ps)); //如果rhs和*this是同一个对象,我们就将从已经释放的内存中拷贝数据
i = rhs.i;
return *this;
}
//定义一个引用计数的类
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&);
~HasPtr();
public:
std::string* ps;
int i;
std::size_t* use; //用来记录有多少个对象共享*ps的成员
};
//析构函数不能无条件的delete ps,可能还有其他对象指向这块内存。析构函数应该递减引用计数,
//指出共享string的对象少了一个,如果计数器变为0,则析构函数释放ps和use指向的内存
HasPtr::~HasPtr() {
if (-- * use == 0) {
delete ps;
delete use;
}
}
/*
赋值运算符必须处理自赋值,我们通过向地震rhs中的计数然后再递减左侧运算对象中的计数来实现着一点
当两个对象相同时,在我们检查ps及use是否应该释放之前,计数器就已经被递增过了
*/
HasPtr& 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;
}
//交换操作
void test02() {
HasPtr v1("world");
HasPtr v2("hello");
HasPtr temp = v1;
v1 = v2;
v2 = temp;
//我们更希望使用swap交换指针而不是分配string的新副本
string* temp = v1.ps;
v1.ps = v2.ps;
v2.ps = temp;
}
//编写我们自己的swap函数
class HasPtr {
friend void swap(HasPtr&, HasPtr&);
};
inline void swap(HasPtr& lhs, HasPtr& rhs) {
using std::swap;
swap(lhs.ps, rhs.ps); //swap函数中的using声明并没有隐藏HasPre版本的swap声明
swap(lhs.i, rhs.i);
}
//错误的swap版本
void swap(Foo& lhs, Foo& rhs) {
std::swap(lhs.h, rhs.h); //这个函数使用了标准库中的swap而不是HasPtr版本
}
//正确的swap版本
void swap(Foo& lhs, Foo& rhs) {
using std::swap;
swap(lhs.h, rhs.h); //使用HasPtr版本的swap
}
int main() {
system("pause");
return 0;
}