C++漫谈:从std::move到移动构造函数

    2021年12月25日 Cpp Cpp 字数:6418

半年之前,写到的一个代码(示例):

vector<string> vc;
vc.push_back(string_value);

因为在搜素场景,耗时毫秒必争。cr的时候,review的同学建议改成:

vector<float> vc;
vc.push_back(move(string_value));

这样修改后的好处:

  • C++ 标准库使用比如vector::push_back等这类函数时,会对参数的对象进行复制,连数据也会复制.这就会造成对象内存的额外创建,本来原意是想把参数push_back进去就行了,通过std::move,可以避免不必要的拷贝操作;
  • std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能;
  • 对指针类型的标准库对象并不需要这么做;

在看move函数的原理之前,先看下面一个例子:

string str1("hello");
string &&str2 = move(str1);
string &&str3 = move(str1);
cout << "str1: " << str1 << endl;
cout << "str2: " << str2 << endl;
cout << "str3: " << str3 << endl;

string str4(str2);
cout << "str1: " << str1 << endl;
string str5(move(str1));
cout << "str1: " << str1 << endl;

打印结果:
str1: hello
str2: hello
str3: hello
str1: hello
str1: 
Program ended with exit code: 9

如果对打印结果感到有点迷惑,再回头看std::move的原理:std::move并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。从实现上讲,std::move基本等同于一个类型转换:static_cast(lvalue)

具体的函数原型:

// 可能的实现, C++14 起
// 返回类型这么写是为了避免转发引用,即保持左值引用不变
templateconstexpr remove_reference_t&&
move(T&& t)  noexcept
{
  return static_cast&&>(t);
}

只是将左值强制转化为右值引用,所以前4个结果都打印hello,就不足为怪了。对于第5个打印的结果,move(str1)返回的右值作为参数,就实现了真正的”move”?这里面,真正实现“move”操作的,以上面的例子为例,是string数据类型的移动构造函数。

为了更形象说明这个问题,看个例子:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
using namespace std;

class Str{
    public:
        char *str;
        Str(char value[])
        {
            cout<<"普通构造函数..."<<endl;
            str = NULL;
            int len = strlen(value);
            str = (char *)malloc(len + 1);
            memset(str,0,len + 1);
            strcpy(str,value);
        }
        Str(const Str &s)
        {
            cout<<"拷贝构造函数..."<<endl;
            str = NULL;
            int len = strlen(s.str);
            str = (char *)malloc(len + 1);
            memset(str,0,len + 1);
            strcpy(str,s.str);
        }
        Str(Str &&s)
        {
            cout<<"移动构造函数..."<<endl;
            str = NULL;
            str = s.str;
            s.str = NULL;
        }
        ~Str()
        {
            cout<<"析构函数"<<endl;
            if(str != NULL)
            {
                free(str);
                str = NULL;
            }
        }
};
int main()
{
    char value[] = "hello world";
    Str s(value);
    vector<Str> vs;
    //vs.push_back(move(s));
    vs.push_back(s);
    cout<<vs[0].str<<endl;
    if(s.str != NULL)
        cout << s.str << endl;
    return 0;
}

重点关注上面Str(Str &&s)移动构造函数,在移动构造函数中,原始指针赋值给了类指针变量,只涉及指针操作,不需要拷贝,所以速度快);同时将原始指针指向空指针。所以在最上面的例子中,最后一个结果,打印出来的空(经过移动构造函数后,原始指针指向空指针)。

移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用。这意味着,移动构造函数的参数是一个右值或者将亡值的引用。也就是说,只用用一个右值,或者将亡值初始化另一个对象的时候,才会调用移动构造函数。而那个move语句,就是将一个左值变成一个将亡值。

参考:C++移动构造函数以及move语句简单介绍

请他喝一杯

取消

感谢您的支持,如有任何问题,您可以在打赏留言中留下您的微信

微信扫码
支付宝扫码

打开微信扫一扫,即可进行在打赏中留言哦