移动语义2: 移动构造

题目描述

当我们复制构造一个对象时,通常会调用形如 B(const B&); 的复制构造函数,这件事情我们已经相当熟悉了。

现在考虑初始化 T obj(expr);。如果 expr 是一个右值表达式——那么如上一题所述,这通常意味着它是一个临时变量;也即 expr 所指代的对象会很快被析构。如果 T 复制的开销高昂(比如层层深复制),那么复制后立即析构不如直接将 expr 的资源移动obj

考察下面的代码。B 持有 A* 指针,B 的复制构造函数实现了对 A 的深复制。我们假设这个复制开销很大,这里用输出 "Copy A" 来代表。目前的代码在编译运行时,不管是用左值还是右值构造 B,都会产生 A 的复制,即输出

Copy A
---
Copy A
---

但我们希望在使用右值 expr 初始化 B obj(expr); 的时候,不是将临时对象内部的 A 深复制一遍给 obj,而是直接将 obj.a 指向临时对象内部的 A(即浅复制;换句话说,反正 expr 很快就会析构掉,直接拿来用就好。)

运用右值引用,编写 B移动构造函数,该函数仅在使用右值初始化 B 时调用。补充代码后,你的程序应当如样例输出所示,在使用右值初始化 B 时不发生 "Copy A" 的输出。我们还会使用内存分配计数器检查你的代码是否存在内存泄露。

关于输入

关于输出

见样例输出

参考答案

#include <cassert>
#include <iostream>

struct A {
    A() = default;
    A(const A& other) {
        std::cout << "Copy A\n";
    }
};

struct B {
    A* a;
    B() : a{new A{}} {}
    B(const B& other) : a{new A(*other.a)} {}
    B(B&& other) : a{other.a} {
        other.a = nullptr;
    }
//
    ~B() {
        delete a;
    }
};

struct C {
    B b;
};

int newCount;

int main() {
    newCount = 0;
    {
        B b;
        B b_copied(b);
        assert(b_copied.a != nullptr);
    }
    std::cout << "---\n";
    assert(newCount == 0);

    {
        B b_moved(C{}.b);
        assert(b_moved.a != nullptr);
    }
    std::cout << "---\n";
    assert(newCount == 0);
}

// 以下是内存分配计数实现,不用管
void* operator new(std::size_t size) {
    if (void* ptr = std::malloc(size)) {
        newCount++;
        return ptr;
    }
    throw std::bad_alloc{};
}
void operator delete(void* ptr, std::size_t) noexcept {
    if (ptr) {
        std::free(ptr);
        newCount--;
    }
}