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