移动语义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--;
}
}