【C++11】类的新功能
目录
默认成员函数
介绍两个关键字:delete 和 default
default:强制生成默认函数的关键字
delete:禁止生成默认成员函数的关键字
默认成员函数
C++11之前只有6个默认成员函数
1. 构造函数
2. 析构函数
3. 拷贝构造函数
4. 拷贝赋值重载
5. 取地址重载
6. const 取地址重载
最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
移动构造
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个(都没实现)。那么编译器会自动生成一个默认移动构造。
默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝(浅拷贝),自定义类型成员,则需要看这个成员是否实现移动构造(和拷贝构造相似) 如果实现了就调用移动构造,没有实现就调用拷贝构造。
移动赋值
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。
默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造 完全类似)
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
注意:如果我们手动实现了移动拷贝或移动赋值,就算我们没有实现拷贝构造和拷贝赋值,编译器也不会去实现默认的拷贝构造和拷贝赋值
我们来看看,默认生成的移动赋值和移动赋值是如何工作的
为了完成测试,我们需要实现一个简化版的string和person类来进行配合完成这次测试
string类:
namespace nxbw
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}// 移动构造string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移动拷贝" << endl;swap(s);}// 移动赋值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动赋值" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}} private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};
}
person类:
class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p):_name(p._name),_age(p._age){}Person& operator=(const Person& p){if(this != &p){_name = p._name;_age = p._age;}return *this;}~Person(){}
private:nxbw::string _name;int _age;
};
情况一:我们实现Person类中的移动构造,移动赋值和析构函数,看Perosn是否会生成默认的移动构造和移动赋值,在主函数mian中,创建一个Person s1,然后将s1转换为右值拷贝构造给s2
int main()
{Person p1("nxbw", 20);Person p2 = move(p1);Person p3;p3 = move(p2);return 0;
}
我们来看看以上代码是否可以调用到默认移动拷贝和移动赋值进行资源的转移
我们打开调试
我们来看看p1的资源是否会转移给p2,p3的资源是否会调用给p1
调试完之后,我们并没有看到资源的转移,而是调用拷贝构造进行的深拷贝,这也间接说明了,只要有拷贝构造,移动构造,析构函数其中任意一个的出现,编译器就不会生成默认的移动构造和移动赋值
情况二:我们将Perosn中的拷贝构造,拷贝赋值和析构函数都注释掉,看看编译器是否会生成默认的移动构造和移动赋值
Person默认生成的移动构造,对于自定义类型来说会去调用它自己的移动构造
对内置类型会进行逐字节拷贝(浅拷贝)
Person默认生成的移动赋值,对自定义类型来说,会调用自己的移动赋值
对内置类型,进行值拷贝
在注释了拷贝构造,拷贝赋值,析构函数之后,经过调试可以发现,Person类中确实生成了默认的移动赋值和移动拷贝
介绍两个关键字:delete 和 default
default:强制生成默认函数的关键字
C++11可以让我们更好的控制要使用的默认成员函数,假设在某些情况下我们需要使用某个默认成员函数,但是因为某些原因导致无法生成这个默认成员函数这时可以使用default关键字强制某个默认成员函数
如下情况:
class Person
{
public:Person(const char* name):_name(name){}
private:nxbw::string _name;
};int main()
{Person p;return 0;
}
实例化一个对象,没有给参数进行构造,编译器会报错,因为我们并没有在Person类中实现默认构造函数,并且我们实现了构造函数,编译器也不会自动生成默认构造函数
这时我们就可以使用default关键字强制生成默认成员函数
class Person
{
public:Person(const char* name):_name(name){}Person() = default;
private:nxbw::string _name;
};int main()
{Person p;return 0;
}
delete:禁止生成默认成员函数的关键字
当我们想要限制某些函数的生成时,可以通过如下两种方式:
C++98中,可以将函数设置为私有,并且只用声明不用定义,这样当外部调用该函数时,就会报错
C++11中,可以加载该函数的声明的后面加上 =delete,意思是让编译器不要生成该函数的默认版本,所以我们将=delete修饰的函数称为删除函数
class Person
{
public:Person() {};Person(const Person&) = delete; //限制默认拷贝构造
private:nxbw::string _name;
};int main()
{Person p1;Person p2(p1);return 0;
}
被=delete修饰的函数可以是公有也可以是私有,效果都一样
看看效果: