C++右值引用和移动构造函数

C++右值引用和移动构造函数,第1张

1.右值引用:

        我们可能知道函数里的临时对象的生存周期在函数内,也可能更详细的知道临时对象离开它的作用域就会消亡,不过我们平时可能很少直观感受到这一点。因此我们想象下面一个场景,如果我们想要定义一个int*类型返回值的函数,该函数构建一个int类型的值。此时我们一般采用如下方式写:

int* func() {
    return new int(1);
}

        这样我们就可以得到一个动态内存,因此我们需要在调用完返回值后delete释放内存(当然可以使用智能指针)。不过很多时候我们不喜欢使用动态内存,然后我们想到了在函数里构建一个int变量并返回其地址,如下:

int* func(bool flag) {
    int a=1;
    return &a;
}

int main() {
    int* p=func(true);
    if(p==nullptr) {
        std::cout<<"nullptr";
    }
    return 0;
}

        这样看起来不错,但是当我们运行起来会发现返回的p是nullptr,而不是1。结合临时对象我们此时可能明白了,a在func结束之后就会被回收。那么此时我们好像只剩下两个选择,要么返回一个new的对象,要么就在函数外构建变量然后函数内赋值。此时我们可能会想如果能够延长a的生存期到p的生存期结束就好了,而右值引用可以延长生存期。

        我们先看看右值引用的基本用法

int a=2;
int&& b=std::move(a);
int *p=b;
std::cout<

        第二行的std::move使得右值引用可以指向左值。我们发现左值引用无法指向右值,但是右值引用通过std::move可以指向左值。右值引用可以指向右值,也可以被指针取地址。我们再用如下方式实现上述问题:

int&& get() {
    int a=1;
    return std::move(a);
}

int* getPtr() {
    int&& a = get();
    return &a;
}

int *p=getPtr();

        此时p已经指向int型的1了,这样我们也直观验证了右值引用可以延长对象生存周期的特点。

2.移动构造函数:

        上文简单介绍了右值引用的一些特点,但是右值引用最大的用处(可能没有之一)在于构建移动构造函数来减少拷贝次数。

2.1 深拷贝:

        简单的说如果类里面有指针,那么浅拷贝就是只拷贝指针,拷贝者和被拷贝者指向同一个地址。深拷贝就是构造一个等大的内存,然后从被拷贝者指针指向的内存复制数据到自己中。如下伪代码:

class node {
public:
    int size;
    int* ptr;
    node() :size(0), ptr(nullptr) {};
    node(const node& otherNode) {
        size = otherNode.size;
        ptr = new int[size];
        for (int i = 0; i < size; i++) {
            ptr[i] = otherNode.ptr[i];
        }
    }
    node& operator=(const node& otherNode) {
        size = otherNode.size;
        ptr = new int[size];
        for (int i = 0; i < size; i++) {
            ptr[i] = otherNode.ptr[i];
        }
    }
};

        我们需要拷贝ptr指向的所有内容,但是很多时候我们赋值之后就不需要nweNode了,因此我们可能会想如果能直接接管newNode的内容就好了。但是我们不能直接让newNode.ptr=nullptr,因为不是所有的赋值 *** 作后newNode就不需要了,因此我们需要使用新的函数进行移动构造。

2.2 尝试使用左值引用来进行移动构造:

        我们第一反应就是构建如下拷贝构造函数:

node(node& otherNode);

        然而我们传递的是左值,这意味着我们不能使用下面方式进行构造:

node a(node());

        因为左值引用不能接收右值,而形如node()这类函数显然是右值。因此我们想到const引用可以指向右值,因此想到下面形式:

node(const node& otherNode);  //与深拷贝重定义,想到下列方式
node(const node& otherNode, char arg);

        我们又得到一个新的方式,但是首先我们需要传递一个无用参数arg,它仅仅用来区分深拷贝。此外,当我们实现时会发现由于是const引用,因此我们不能修改otherNode,进而就不能写以下语句:

otherNode.ptr=nullptr;

        这样左值引用方式基本宣告失败。因此引入了右值引用。

2.3 右值引用实现的移动构造函数:

        我们直接上代码:

node(node&& otherNode) {
    size = otherNode.size;
    ptr = otherNode.ptr;
    otherNode.ptr=nullptr;
}
node& operator=(node&& otherNode) {
    size = otherNode.size;
    ptr = otherNode.ptr;
    otherNode.ptr=nullptr;
}

        这样我们就成功接管了otherNode,没有使用任何拷贝。因此我们可以如下方式调用:

node a;
node b;
node c=a;
node d=std::move(b);

        第三行为深拷贝,第四行为浅拷贝,直接接管b。

2.4 移动构造函数的注意事项:

        当我们没有定义移动构造函数时,我们采用移动拷贝时会执行默认的拷贝构造函数,即便我们使用下面写法:

node(node&& otherNode)=default;

        它调用的仍是默认的拷贝构造函数node(const node& otherNode),当默认拷贝构造函数=delete时它也无法调用。但是当默认拷贝构造函数delete时,我们可以写移动构造函数并进行移动构造。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/741090.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-28
下一篇 2022-04-28

发表评论

登录后才能评论

评论列表(0条)