【C++】C++11——新的类功能|default、delete|可变参数模板|emplace

一、新的类功能

原来C++类中,有6个默认成员函数:

  • 构造函数、析构函数、拷贝构造函数、拷贝赋值函数、取地址重载、cosnt取地址重载

前4个比较重要,后面两个默认成员函数一般不会用到

但是C++11新增加了两个默认成员函数:移动构造函数和移动赋值运算符重载

移动构造函数和移动赋值运算符重载的生成:

如果没有自己实现移动构造函数:并且没有实现析构函数、拷贝构造和拷贝赋值重载中的任意一个,那么编译器就会自动生成一个默认移动构造函数。默认生成的移动构造函数,对于内置类型成员会完成值拷贝(浅拷贝),对于自定义类型成员,如果实现了移动构造,就调用移动构造,没有实现就调用拷贝构造

如果没有自己实现移动赋值运算符重载:并且没有实现析构函数、卡搜被构造、拷贝赋值重载中的任意一个,那么编译器就会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型完成值拷贝(浅拷贝),对于自定义类型成员,如果实现了移动赋值,就调用移动赋值,没有实现就调用拷贝构造(默认移动赋值与上面的移动构造类似)

最后,如果提供了移动构造和移动赋值,编译器就不会自动提供拷贝构造和拷贝赋值

  • 默认生成的移动构造与默认生成的移动赋值运算符
//默认生成移动构造
class Person
{
public:
	Person(const char*name="",int age = 0)
		:_name(name)
		,_age(age)
	{}
private:
	hwc::string _name;//调用hwc中的string的移动构造与移动赋值
	int _age;//值拷贝
};


int main()
{
	Person s1;
	Person s2 = s1;//左值,拷贝构造。
	Person s3 = std::move(s1);//右值,移动构造
    Person s4;
	s4 = std::move(s2);//移动赋值
	return 0;
}

image-20230312101835436

image-20230312104004715


二、类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这个我们在类和对象就有说过了。

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

C++11可以更好的控制使用的默认函数。当我们要使用某个默认的函数,但是因为一些原因这个函数没有默认生成:比如我们提供了析构函数,就不会有移动构造默认生成了。此时我们就可以使用default这个关键字来显示指定移动构造的生成

class Person
{
public:
	Person(const char*name="",int age = 0)
		:_name(name)
		,_age(age)
	{}

	/*
	自己实现移动构造比较麻烦
	 Person(Person&&p)
		:_name(std::forward<hwc::string>(p._name))
		,_age(p._age)
	{}
	*/
	//移动构造强制生成
	Person(Person&& p) = default;

	//拷贝构造
	Person(const Person&p)
		:_name(p._name)
		,_age(p._age)
	{}

	~Person()
	{}
private:
	hwc::string _name;//调用hwc中的string的移动构造与移动赋值
	int _age;//值拷贝
};
int main()
{
	Person s1;
	Person s2 = s1;//左值,拷贝构造。
	Person s3 = std::move(s1);//右值,移动构造
	return 0;
}

image-20230313075006275

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

在C++98中,如果要限制某些函数的生成,把该函数设置成private,并且只声明不实现

//不想让A类对象拷贝
class A
{
public:
	void func()
	{
		A tmp(*this);
	}
	A()
	{}
	~A()
	{
		delete[] p;
	}
private:
	//只声明不实现,声明为私有
	A(const A& aa);
private:
	int* p = new int[10];
};

在C++11中,只需要在该函数声明上加上=delete即可,该语法指示编译器不生成对应函数的默认版本,=delete修饰的函数为删除函数

//不想让A类对象拷贝
class A
{
public:
	void func()
	{
		A tmp(*this);
	}
	A()
	{}
	~A()
	{
		delete[] p;
	}
	A(const A& aa) = delete;
private:
	int* p = new int[10];
};
int main()
{
	A aa1;
	aa1.func();
	//A aa2(aa1);
	return 0;
}

三、可变参数模板

可变参数模板是C++11新增的特性之一,能够让我们创建可以接收可变参数的函数模板和类模板

1.可变参数的函数模板

可变参数模板定义:

template<class ...Args>
void ShowList(Args... args)
{

}

Args是一个模板参数包,args是一个函数形参参数包,声明一个参数包Arag…args,这个参数包中可以包含0到任意个模板参数

template<class ...Args>
void ShowList(Args... args)
{
}
int main()
{
	ShowList();
	ShowList(1);
	ShowList(1, 1.2);
	ShowList(1, 1.3, string("xxxxx"));
	return 0;
}

可以通过sizeof算出参数包中参数的个数,计算的是参数个数:...是在外面的

template<class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}

如何获取参数包中的每个参数?

我们以前都是习惯[],但是这里语法并不支持使用 args[i] 的方式来获取参数包中的参数,只能通过展开参数包的方式来获取,这是使用可变参数模板的一个主要特点

下面是错误示范:

template<class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
	for (int i = 0; i < sizeof...(args); i++)
	{
		cout << args[i] << " "; 
	}
	cout << endl;
}

但是C++并不支持这种方法

2.参数包的展开

  • 递归展开

函数递归方式展开:

先给可变参数的函数模板增加一个模板参数class T,从接收的参数包中把第一个参数分离出来

在函数模板中递归调用该函数模板,调用时传入的剩下的参数包

直到递归到参数包为空,退出递归。

void ShowList()
{
	cout << endl;
}
template<class T,class ...Args>
void ShowList(T val,Args... args)
{
	cout << val << " ";
	ShowList(args...);
}

image-20230321203739968

  • 逗号表达式展开

逗号表达式是会从左到右依次计算各个表达式,并将最后一个表达式的值作为返回值返回:我们将最后一个表达式设为整型值,所以最后返回的是一个整型;将处理参数个数的动作封装成一个函数,将该函数作为逗号表达式的第一个表达式;…代表参数包,列表展开;另外,我们要的是打印出参数包中的各个参数,因此处理函数PrintArg当中要做的就是将传入的参数进行打印即可:

template <class T>
void PrintArg(T t)
{
	cout << t << " ";
}

template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args),0)... };
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 1.2);
	ShowList(1, 1.3, string("xxxxx"));
	return 0;
}

image-20230321204718956

我们也可以这样子写,不用逗号表达式:直接展开参数包初始化数组

template <class T>
int PrintArg(T t)
{
	cout << t << " ";
	return 0;
}

template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { PrintArg(args)... };
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 1.2);
	ShowList(1, 1.3, string("xxxxx"));
	return 0;
}

image-20230321205624350


四、emplace

C++11给STL容器新增加了emplace的相关接口,比如list容器的push_front、push_back、insert都有了对应的emplace_front、emplace_back、emplace:

image-20230321193903521

这些emplace相关的接口也支持了模板的可变参数,比如vector容器的emplac函数的声明如下:

image-20230321194215069

1.使用

push_back与emlace_back对于内置类型并没有什么区别,emplace_back 插入元素时,也可以传入左值对象或右值对象,但不可以使用列表进行初始化,而push_back是可以使用列表初始化的:

int main()
{
	list<int> list1;
	list1.push_back(1);
	list1.emplace_back(2);
	list1.emplace_back();
	for (auto e : list1)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

image-20230321211249614

emplace系列接口最大的特点就是,插入元素可传入用于构造元素的参数包

int main()
{
	std::list<std::pair<int, char >> mylist;
	mylist.push_back(make_pair(1, 'a'));//构造+拷贝构造
    //mylist.push_back(1,'a');错误,不支持
	mylist.emplace_back(1, 'a');//直接构造
	return 0;
}

2.意义

emplace接口的可变参数模板是万能引用,既可以接收左值,也可以接收右值,同时还可以接收参数包

如果调用emplace接口是传入的参数是参数包,那就可以调用行函数进行插入,最终定位new表达式调用构造函数对空间进行初始化,匹配到构造函数

调用 emplace 接口时传入的是左值或者右值,首先需要先在此之前调用构造函数实例化出一个对象,最后使用定位 new 表达式调用构造函数对空间进行初始化时,会匹配到拷贝构造或者移动构造

也就是说,如果传入的是参数包,只会调用构造函数

class Date
{
public:
	Date(int year=1, int month=1, int day=1)
		:_year(year)
		, _month(month)
		, _day(_day)
	{
		cout << "构造" << endl;
	}
	Date(const Date& d)
		:_year(d._year)
		,_month(d._month)
		,_day(d._day)
	{
		cout << "拷贝构造" << endl;
	}
	void swap(Date& d)
	{
		std::swap(_year, d._year);
		std::swap(_month, d._month);
		std::swap(_day, d._day);
	}
	Date(Date&& d)
	{
		cout << "移动构造" << endl;
		swap(d);
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	list<Date> lt1;
	Date d1(2, 2, 2);
	lt1.emplace_back(d1);//左值
	cout << "----------------------------------" << endl;
	lt1.emplace_back(20, 20, 20);//参数包
	cout << endl;
	Date d2(3, 3, 3);
	lt1.push_back(d2);//左值
	cout << "----------------------------------" << endl;
	lt1.push_back(Date(1, 1, 1));//右值
	cout << "----------------------------------" << endl;
	lt1.push_back({ 3,3,3 });//右值
	return 0;
}

image-20230322094017960

emplace 最大特点就是支持传入参数包,用这些参数包直接构造出对象,这样就能减少一次拷贝,这就是为什么有人说 emplace 系列接口更高效的原因。

猜你喜欢

转载自blog.csdn.net/weixin_60478154/article/details/129704725
今日推荐