C++11(15)(中)

        https://blog.csdn.net/qscftqwe/article/details/156025461

        上节课链接,二者搭配使用更佳,强烈推荐看一下,就看一下呗,感谢感谢了!

一.右值引用使用场景和意义

        前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

        左值引用的使用场景:

        做参数和做返回值都可以提高效率。

void func1(bit::string s)
{}
void func2(const bit::string& s)
{}
int main()
{
    bit::string s1("hello world");
    // func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
    func1(s1);
    func2(s1);
}

        左值引用的短板:

        但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回只能传值返回。例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

        在函数中传值返回是返回一个临时对象,如果该临时对象符合将亡值的概念那么就可以采用移动构造而不采取拷贝构造(前提是有移动构造)

        右值引用和移动语义解决上述问题:

        在bit::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占为己有,那么就不用深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己

        不仅仅有移动构造,还有移动赋值:

        在bit::string类中增加移动赋值函数,再去调用bit::to_string(1234),不过这次是将bit::to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动赋值。

// 赋值重载
string& operator=(string s)//这边参数是值传递会调用拷贝构造
{
    swap(s);
    return *this;
}
// 移动赋值
string& operator=(string&& s)
{
    cout << "string& operator=(string&& s) -- 移动语义" << endl;
    swap(s);
    return *this;
}
int main()
{
    bit::string ret1;
    ret1 = bit::to_string(1234);
}

        因此右值引用解决的场景:

        正是自定义中需要深拷贝类,且只能用传值返回和值传递(即不能用左值引用的情况)我们会用右值引用,以及独占资源的类(该类对象不能被拷贝,只能被转移)我们也只能用右值引用。

        1.1 右值引用引用左值及其一些更深入的使用场景分析

        按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值

        C++11中,std::move()函数位于头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义


        move的理解:

        move不会改变左值属性,而是返回基于该左值为模板返回的右值

        STL容器插入接口函数也增加了右值引用版本:
 

void push_back (value_type&& val);
int main()
{
   list<bit::string> lt;
   bit::string s1("1111");

   // 这里调用的是拷贝构造
   lt.push_back(s1);

   // 下面调用都是移动构造
   lt.push_back("2222");//隐式类型构造也当右值处理
   lt.push_back(std::move(s1));
   return 0;
}
运行结果:
// string(const string& s) -- 深拷贝
// string(string&& s) -- 移动语义
// string(string&& s) -- 移动语义

二.完美转发

          模板中的&&万能引用

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; }

// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,

// 引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值引用
//(右值引用的属性是左值引用),因此下面即使类型是右值,不过全都会调用左值

// 如果希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
 
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}

int main()
{
   PerfectForward(10);           // 右值

   int a;
   PerfectForward(a);            // 左值
   PerfectForward(std::move(a)); // 右值

   const int b = 8;
   PerfectForward(b);      // const 左值
   PerfectForward(std::move(b)); // const 右值
}

        注意:上面注释是右值的也会被当作左值处理        

        std::forward 完美转发在传参的过程中保留对象原生类型属性

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));
}
int main()
{
	PerfectForward(10);           // 右值

   int a;
   PerfectForward(a);            // 左值
   PerfectForward(std::move(a)); // 右值

   const int b = 8;
   PerfectForward(b);      // const 左值
   PerfectForward(std::move(b)); // const 右值
}

         注意:这个处理就是你左值就左值处理,右值就右值处理

        完美转发总结:函数模板 + 转发引用(T&&)+ std::forward

        它的作用是:在泛型编程中,无损传递实参的类型和值类别。

三.类的新功能

        默认成员函数:

        C++11 新增了两个:移动构造函数和移动赋值运算符重载

        针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

        如果你没有声明析构函数 、拷贝构造、赋值重载、移动构造、移动赋值。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,内置类型直接复制值(源对象不变),自定义类型成员,则需要看这个成员是否实现移动构造/赋值,如果实现了就调用移动构造/赋值,没有实现就调用拷贝构造。

        类成员变量初始化

        C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。

        强制生成默认函数的关键字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)
    {}
   Person(Person&& p) = default;
private:
	string _name;
	int _age;
};

        继承和多态中的final与override关键字

        这个我们在继承和多态章节已经进行了详细讲解,因此不做讲解。

        关于今天的部分这么说呢,就是理论部分很强,而且会存在不好理解,可能需要多看几遍,尤其是右值引用的重要性完美要了解清楚,最后就是关于理论部分学习,看不够还是要自己理一边,用自己的话说出来,有的东西懂了就是懂了,不懂就是不懂,不能似懂非懂如囫囵吞枣一般!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值