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


完~

未经作者同意禁止转载

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水墨不写bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值