类的新功能

本文详细介绍了C++11中新增的移动构造函数和移动赋值重载函数,以及它们与默认成员函数的区别。并通过示例展示了如何通过实现和禁止生成默认函数来控制类的行为,如final和override关键字在继承和多态中的应用。
摘要由CSDN通过智能技术生成

类的新功能

默认成员函数

在C++11之前,一个类中有如下六个默认成员函数:

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

其中前四个默认成员函数最重要,后面两个默认成员函数一般不会用到,这里默认成员函数是我们不写编译器会自动生成的函数。在C++11标准中又增加了两个默认成员函数,分别是移动构造函数和移动赋值重载函数。

移动构造函数的生成条件:自己没有实现移动构造函数,并且自己没有实现析构函数拷贝构造函数赋值重载函数
移动赋值重载函数的生成条件:自己没有实现移动赋值重载函数,并且自己没有实现析构函数拷贝构造函数赋值重载函数

因此我们需要注意的是:移动构造和移动赋值的生成条件与之前六个默认成员函数不同,并不是单纯的没有实现移动构造和移动赋值,编译器就会默认生成

默认生成的移动构造函数:对于内置类型的成员会完成值拷贝(浅拷贝),对于自定义类型的成员,如果该成员实现了移动构造就调用它的移动构造,否则就调用它的拷贝构造。
默认生成的移动赋值重载函数:对于内置类型的成员会完成值拷贝(浅拷贝),对于自定义类型的成员,如果该成员实现了移动赋值就调用它的移动赋值,否则就调用它的赋值重载。

我们实现一个简化版的string来验证默认生成的移动构造和移动赋值重载函数的功能实现。

namespace a
{
	class string
	{
	public:
		//构造函数
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		//交换两个对象的数据
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//拷贝构造函数(现代写法)
		string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			string tmp(s._str);
			swap(tmp);
		}
		//移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);
		}
		//赋值重载函数(现代写法)
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 深拷贝" << endl;

			string tmp(s);
			swap(tmp);
			return *this;
		}
		//移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}
		//析构函数
		~string()
		{
			//delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

然后我们再编写一个简单的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:
	a::string _name;
	int _age; 
};

虽然Person类当中没有实现移动构造和移动赋值,但拷贝构造、拷贝赋值和析构函数Person类都实现了,因此Person类中不会生成默认的移动构造和移动赋值,可以通过下面的代码来验证:

int main()
{
	Person s1("张三", 21);
	Person s2 = move(s1); //想要调用Person默认生成的移动构造

	return 0;
}

上述代码中用一个右值去构造s2对象,但由于Person类没有生成默认的移动构造函数,因此这里会调用Person的拷贝构造函数(拷贝构造既能接收左值也能接收右值),这时在Person的拷贝构造函数中就会调用string的拷贝构造函数对name成员进行深拷贝。

如果要让Person类生成默认的移动构造函数,就必须将Person类中的拷贝构造、拷贝赋值和析构函数全部注释掉,这时用右值去构造s2对象时就会调用Person默认生成的移动构造函数。

  • Person默认生成的移动构造,对于内置类型成员age会进行值拷贝,而对于自定义类型成员name,因为我们的string类实现了移动构造函数,因此它会调用string的移动构造函数进行资源的转移。
  • 而如果我们将string类当中的移动构造函数注释掉,那么Person默认生成的移动构造函数,就会调用string类中的拷贝构造函数对name成员进行深拷贝。

要验证Person类中默认生成的移动赋值函数可以用下面的代码,验证方式和上面验证移动构造的方式是一样的。

int main()
{
	Person s1("张三", 21);
	Person s2;
	s2 = std::move(s1); //想要调用Person默认生成的移动赋值

	return 0;
}

类成员变量初始化

默认生成的构造函数,对于自定义类型的成员会调用其构造函数进行初始化,但并不会对内置类型的成员进行处理。于是C++11支持非静态成员变量在声明时进行初始化赋值,默认生成的构造函数会使用这些缺省值对成员进行初始化。

class Person
{
public:
	//...
private:
	//非静态成员变量,可以在成员声明时给缺省值,用于给默认构造函数进行初始化
	string _name = "张三";
	int _age = 20;
	static int _n; //静态成员变量不能给缺省值
};

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

C++11可以让我们更好地控制要使用的默认成员函数,假设在某些情况下我们需要使用某个默认成员函数,但是因为某些原因导致无法生成这个默认成员函数,这时可以使用default关键字强制生成某个默认成员函数。下面我们来看一个场景。

class Person
{
public:
	//拷贝构造函数
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}
private:
	string _name; //姓名
	int _age;         //年龄
};

这时就会报错,因为Person类中已经有了拷贝构造函数,导致无法生成默认构造函数,因为默认构造函数生成的条件是没有编写任意类型的构造函数,包括拷贝构造函数。

int main()
{
	Person s; //没有合适的默认构造函数可用

	return 0;
}

这时我们就可以使用default关键字强制生成默认的构造函数。

class Person
{
public:
	Person() = default; //强制生成默认构造函数

	//拷贝构造函数
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}
private:
	string _name; 
	int _age;         
};

 默认成员函数都可以用default关键字强制生成,包括移动构造和移动赋值。

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

当我们想限制默认函数生成,可以通过如下两种方式:

  1. 在C++98中,可以将该函数设置成私有,并且只用声明不用定义,这样当外部调用该函数时就会报错。
  2. 在C++11中,可以在该函数声明后面加上=delete,表示让编译器不生成该函数的默认版本,我们将=delete修饰的函数称为删除函数。


例如,要让一个类不能被拷贝,可以用=delete修饰将该类的拷贝构造和赋值重载。

class CopyBan
{
public:
	CopyBan()
	{}
private:
	CopyBan(const CopyBan&) = delete;
	CopyBan& operator=(const CopyBan&) = delete;
};

注意:被=delete修饰的函数可以设置为公有,也可以设置为私有,效果都一样。

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

被final修饰的类叫做最终类,最终类无法被继承。

class NonInherit final //被final修饰,该类不能再被继承
{
	//...
};

final修饰虚函数,表示该虚函数不能再被重写,如果子类继承后重写了该虚函数则编译报错。

//父类
class Person
{
public:
	virtual void Print() final //被final修饰,该虚函数不能再被重写
	{
		cout << "hello Person" << endl;
	}
};
//子类
class Student : public Person
{
public:
	virtual void Print() //重写,编译报错
	{
		cout << "hello Student" << endl;
	}
};

override修饰子类的虚函数,检查子类是否重写了父类的某个虚函数,如果没有重写则编译报错。

//父类
class Person
{
public:
	virtual void Print()
	{
		cout << "hello Person" << endl;
	}
};
//子类
class Student : public Person
{
public:
	virtual void Print() override //检查子类是否重写了父类的某个虚函数
	{
		cout << "hello Student" << endl;
	}
};
  • 34
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值