【 C++11 】类的新功能

目录

1、准备条件

2、默认成员函数

3、类成员变量初始化

4、强制生成默认函数的关键字default

5、禁止生成默认函数的关键字delete

6、继承和多态中的final与override关键字


1、准备条件

下面的测试案例需用用到移动构造和移动赋值的测试代码,我们给出上篇博文封装过移动构造和移动赋值的简化版string类放到下面,以方便后续的测试代码中进行调用观察现象:

namespace cpp
{
	class string
	{
	public:
        // 构造函数
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// 交换两个对象的数据
		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;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}

2、默认成员函数

原来C++类中(C++11之前),有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个:移动构造函数和移动赋值运算符重载。针对移动构造和移动赋值,编译器也会默认生成,不过生成的条件极其苛刻,下面展开来讨论:

①、移动构造:

  • 如果你没有自己实现移动构造函数,且均没有实现析构函数拷贝构造拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

②、移动赋值:

  • 如果你没有自己实现移动赋值重载函数,且均没有实现析构函数 拷贝构造拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

③、总结:

  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

④、示例:

  • 对于如下的Person类,我们不需要写析构、拷贝构造、赋值重载,因为Person的成员变量_name是自定义类型,会自动去调用string类的拷贝构造、析构、赋值重载完成深拷贝。而内置类型_age完成值拷贝即可。
// 以下代码在vs2013中不能体现,在vs2019及以上的编译器下才能演示体现上面的特性。
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:
	cpp::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;//拷贝构造
	Person s3 = std::move(s1);//移动构造
	Person s4;
	s4 = std::move(s2);//移动赋值
	return 0;
}

因为我们都没写拷贝构造、析构、赋值,所以编译器会默认生成移动构造和移动赋值运算符重载。针对main函数的测试用例,很明显,s2 = s1是拷贝构造,下面的两个分别调用移动构造和移动赋值:

但凡我把Person类中的任何一个拷贝构造或析构或赋值放出来,结果都是去调用string类的拷贝构造函数去完成深拷贝:


3、类成员变量初始化

默认生成的构造函数,对于自定义类型会自动调用它的构造函数进行初始化,对于内置类型并不会进行处理,于是C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这个我们在类和对象默认就讲了,这里再简要提下。

class Person
{
public:
	//……
private:
	//C++11允许非静态成员变量在声明时进行初始化赋值
	cpp::string _name = "王五";
	int _age = 19;
	static int _num;//静态成员变量不能给缺省值
};

4、强制生成默认函数的关键字default

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定生成移动构造或移动赋值

  • 示例:如下我们实现了拷贝构造,所以编译器就不会生成移动构造和移动赋值了,会去调用自定义类型string类的拷贝构造函数完成深拷贝:
class Person
{
public:
	//构造函数
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	//拷贝构造
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}
private:
	cpp::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

为了让编译器生成移动构造和移动赋值,我们可以使用default关键字显示指定生成移动构造或移动赋值:

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(Person&& pp) = default;
	//强制生成移动赋值
	Person& operator=(Person&& pp) = default;
private:
	cpp::string _name;
	int _age;
};


5、禁止生成默认函数的关键字delete

在C++中,如果想禁止生成默认成员函数,我们有如下两种方式:

  1. 在C++98中,是该函数设置成private,并且只是声明不定义,这样只要其他人想要调用就会报错。
  2. 在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数

示例:我不想让一个类被拷贝,那么在拷贝构造声明的后面加上=delete即可:


6、继承和多态中的final与override关键字

  • 这个我们在继承和多态章节已经进行了详细讲解,这里再强调下:

1、final:修饰虚函数,表示该虚函数不能再被重写,修饰类表示不能被继承。

  • 这里我父类的虚函数Drive不想被其它人重写,在其后面加上final即可,此时子类就无法对Drive进行重写了,如下:

final修饰一个类,让其不能被继承,如下:

2、override:

  • override的作用是检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三分苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值