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

[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.kler.cn/a/281270.html

相关文章:

  • 【C#】【EXCEL】Bumblebee/Classes/Marshal2.cs
  • mongodb日期转换
  • Linux (centos ) 安装和使用 ffmpeg 教程
  • 苹果裁员与服务战略调整:科技巨头应对市场挑战的新动向
  • 【笔记】0基础python学爬虫(未完)
  • 网络基础:理解IP地址、默认网关与网段(IP地址是什么,默认网关是什么,网段是什么,IP地址、默认网关与网段)
  • Visual Basic:企业级应用开发的稳健之选
  • js中<script> 标签中type值及其含义
  • Sentinel-1 Level 1数据处理的详细算法定义(八)
  • 什么样的数据摆渡系统才是业务部门需要的?
  • Spark-RDD迭代器管道计算
  • Codeforces 1304C - Air Conditioner(1500)
  • Gartner发布SBOM软件物料清单创新洞察:SBOM的三种标准、五个应用场景及实施成功的四个关键
  • Linux并发与竞争
  • 如何使用ssm实现图书商城网站的设计和开发+vue
  • 什么情况用Bert模型,什么情况用LLaMA、ChatGLM类大模型,咋选?
  • Python学习-数据库操作
  • Linux Shell脚本入门:参数符号$0,$1,$#,$@,$$与模式替换符^^,,的实用手册
  • comfyUI使用flux模型报错got promptUsing pytorch attention in VAE,
  • CSS3实现购物车动画效果