当前位置: 首页 > news >正文

[C++] C++11详解 (三)类的成员函数、完美转发

标题:[C++] C++11详解 (三)完美转发与lambda表达式

@水墨不写bug



目录

一、C++11新增两个类的默认成员函数

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

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

二、完美转发


正文开始:

一、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()时,就会正确匹配对应的函数了。


完~

未经作者同意禁止转载


http://www.mrgr.cn/news/13819.html

相关文章:

  • 【pgAdmin4】创建/删除:数据库Database和数据库表Table
  • 【Python机器学习】NLP词中的数学——向量化
  • 使用Jmeter压测dubbo接口(不依赖dubbo插件)
  • EmguCV学习笔记 VB.Net 6.6 图像的矩
  • Jenkins安装使用详解,jenkins实现企业级CICD流程
  • Elasticsearch的部署和使用
  • 「养宠干货」为什么猫咪吃罐头更好?高性价比主食罐推荐
  • vue3中批量下载文件(压缩包)功能
  • 矩阵分块乘法的证明
  • 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch12 随机森林(Random Forest)
  • 基于协同过滤算法Spring Boot+Vue的图书商城系统
  • ## 已解决:亲测有效的 org.xml.sax.SAXNotRecognizedException 异常解决方法
  • 独家!汕尾广投荣得ICAS英格尔认证颁发的ESG认证,引爆基建行业绿色革命浪潮
  • 微服务面试题
  • 【Rust光年纪】深度解读:Rust语言中各类消息队列客户端库详细对比
  • FastAPI vs Flask: 专业对比与选择
  • javaEE
  • 回归分析系列14.2— 正则化回归
  • CSS(层叠样式表)
  • 游戏开发的双刃剑:Visual Basic在游戏开发中的局限与机遇