标题:[C++] C++11详解 (三)完美转发与lambda表达式
@水墨不写bug
目录
正文开始:
一、C++11新增两个类的默认成员函数
在之前的讲解中:《【Cpp】类和对象#拷贝构造 赋值重载_cpp类重载-CSDN博客》、
《【Cpp】类和对象#构造函数 析构函数_cpp类中析构函数-CSDN博客》,我们知道在C++11之前,类的成员函数有默认的6个,分别是:
1.构造函数
2.析构函数
3.拷贝构造
4.赋值拷贝
5.取地址重载
6.const取地址重载
在C++11又新增了两个默认成员函数:移动构造和移动赋值 。
但是想要让编译器自己生成这两个默认成员函数,是有一定的规则的:
如果没有手动实现移动构造,并且没有实现析构函数、拷贝构造、赋值拷贝这三个函数中的任意一个(这三个只要存在一个,就不满足条件),编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对内置类型逐字节拷贝,自定义类型则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,如果没有实现,就调用拷贝构造。
完全类似的,如果没有手动实现移动赋值,并且没有实现析构函数、拷贝构造、赋值拷贝这三个函数中的任意一个(这三个只要存在一个,就不满足条件),编译器会自动生成一个默认移动赋值。默认生成的移动赋值函数,对内置类型逐字节拷贝,自定义类型则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,如果没有实现,就调用拷贝赋值。
如果自己手动提供了移动构造或者移动赋值,编译器不会再自动提供。
其实,仔细一想,上面的生成移动赋值的条件看似苛刻,但是确实是有逻辑支撑的:
如果我们手动实现了一个类的析构函数,这表示这个类内部有资源需要清理,同时意味着在拷贝的时候需要深拷贝,也就必须手动实现拷贝赋值和拷贝构造了。析构函数、拷贝赋值、拷贝构造是三位一体的。手动实现了这三个函数之后,移动构造与移动赋值自然也需要手动实现了。
所以,如果不需要手动实现,则这个类就只有构造函数。其他的默认成员函数都是默认生成的。比如:
class Person { public: Person(const char* name = "", int age = 0) :_name(name) , _age(age) {} private: bit::string _name; int _age; };
这个类虽然只手动实现了构造函数,符合生成移动构造和移动赋值的条件,编译器会默认生成拷贝构造,拷贝赋值,析构函数,移动构造,移动赋值;并且这些函数的默认生成都是正确的。
1.强制生成默认函数的关键字default:
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。(比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用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) {} //由于拷贝构造手动实现,无法生成默认移动构造 //可通过关键字:default 强制编译器生成一份移动构造 Person(Person&& p) = default;//这里仅仅是为了举例演示,实际项目中不会这样使用 private: bit::string _name; int _age; };
2.禁止生成默认函数的关键字delete:
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补不定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数,阻止编译器默认生成。
二、完美转发
模板中的&& 万能引用
什么是万能引用?当我们在实现函数前面加上模板参数,并把参数类型设为模板参数&&,这就表示t可以同时接受左值引用和右值引用,这就是万能引用或者引用折叠。
template<typename T> void PerfectForward(T&& t) { //t可以同时接受左值引用和右值引用 }
注意:
模板中的&&并不是表示右值引用,而是表示万能引用,其既能接受左值,又能接受右值。传入什么,就是t的类型就推导为什么的引用。
我们知道,如果我们对一个右值引用,int&&pr = 10;虽然10是右值,但是10的引用pr缺退化为了左值。为了避免引用在传参的时候右值退化为左值,就需要完美转发:
std::forward 完美转发在传参的过程中保留对象原生类型属性
如果没有完美转发,t的类型可以是左值引用或者右值引用。由于右值引用本身的属性是左值,所以如果不用完美转发,则调用func(t),都只会匹配到左值版本。
void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
Fun(t);
}
如果在调用Fun(t)时,对t完美转发:(保持属性)
void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
Fun(std::forward<T>(t));
}
这时,在调用Fun()时,就会正确匹配对应的函数了。
完~
未经作者同意禁止转载