Saber2pr's Blog

右值引用与移动语义

值传递

函数参数传值会发生拷贝,例如:

#include <iostream>

namespace Log {
  void print(std::string value){
      std::cout << value << std::endl;
  }
}

int main(int argc, char *argv[])
{
    std::string hello = "hello world!";
    Log::print(hello);
    return 0;
}

当变量 hello 传给函数 print 时,会先将 hello 的值拷贝一份赋给 print 函数的形式参数 value。

对于其他语言,string 作为基本类型也都是值传递。但是 C/C++可以将其变为引用传递,包括 int 类型等其他任意类型,本质是取地址即指针传递。

引用传递

当然在 C++中也有引用,只是默认值传递。

使用引用:

#include <iostream>

namespace Log {
  // 形参为引用类型,传递地址,不发生拷贝
  void print(std::string& value){
      std::cout << value << std::endl;
  }
}

int main(int argc, char *argv[])
{
    std::string hello = "hello world!";
    // 定义指向变量hello的引用
    std::string& hello_ref = hello;
    Log::print(hello_ref);
    return 0;
}

这样的话,就可以避免发生拷贝,减少内存占用。

常引用

上面的例子有时候不免太麻烦了,定义了变量后,还要在定义一个它的引用,然后后续使用的全是它的引用(和指针差不多)。实际上可以两步合起来,使用常引用即可:

#include <iostream>

namespace Log {
  // 形参为常引用类型
  void print(const std::string& value){
      std::cout << value << std::endl;
  }
}

int main(int argc, char *argv[])
{
    // 定义常引用hello
    const std::string& hello = "hello world!";
    Log::print(hello);
    return 0;
}

C++不允许这样的定义:

std::string& hello = "hello world!";

这是不安全的,必须标注 const,常引用可以保证引用指向不丢失。

常引用可以避免值拷贝,以地址传递。但缺点是,由于 const 导致不能对引用指向的值做修改。

右值引用与移动语义

C++11 引入的重要特性。解决了常引用不能对引用指向的值做修改的问题,又兼有引用传递的优点。

右值就是“右边的值”,在传递的过程中不会被拷贝,只传递其地址。但又可以修改指向的值。

例如:

#include <iostream>
#include<utility>

namespace Log {
  void print(std::string&& value){
      value += "test";
      std::cout << value << std::endl;
  }
}

int main(int argc, char *argv[])
{
    std::string hello = "hello world!";

    Log::print(std::move(hello));
    std::cout << hello << std::endl;
    return 0;
}

输出:

hello world!test
hello world!test

可以看到 [引用 value] 指向的 [变量 hello] 的值被修改了。

分析过程:

首先定义了 std::string 类型变量 hello,然后使用移动语义将其强制转为右值,匹配 Log::print 的函数签名成功,将 hello 变的地址赋给形参 value,Log::print 函数内部通过 value 即 hello 变量的地址修改了 hello 变量的值。

右值引用与常引用重载

函数重载会考虑参数的个数和类型,其中右值引用和常引用的类型是不同的,所以可以发生重载。

例如:

#include <iostream>
#include<utility>

namespace Log {
    void print(const std::string& value){
        std::cout<< "const ref: " << value << std::endl;
    }

    void print(std::string&& value){
      std::cout<< "right ref: " << value << std::endl;
    }
}

int main(int argc, char *argv[])
{
    std::string hello = "hello world!";

    Log::print(hello);
    Log::print(std::move(hello));
    return 0;
}

输出:

const ref: hello world!
right ref: hello world!

看到使用了移动语义的匹配了右值引用的签名。