复制语义和移动语义

复制语义(Copy Semantics)涉及创建对象的深层次副本,包含对其所有成员(包括指向的动态资源)的复制。移动语义(Move Semantics)则是C++11引入的一种优化,它允许“窃取”临时对象的资源,避免不必要的复制。

以下是一个简单的Vector类的例子,该类具有动态分配的数组,用以展示复制语义和移动语义的区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <algorithm> // For std::copy
#include <utility> // For std::move

class Vector {
public:
// 构造函数
Vector(size_t size) : size_(size), data_(new int[size]) {}

// 析构函数
~Vector() { delete[] data_; }

// 拷贝构造函数(复制语义)
Vector(const Vector& other) : size_(other.size_), data_(new int[other.size_]) {
std::copy(other.data_, other.data_ + size_, data_);
}

// 移动构造函数(移动语义)
Vector(Vector&& other) noexcept: size_(other.size_), data_(other.data_) {
other.size_ = 0; // 将other对象置于有效且确定的状态
other.data_ = nullptr;
}

// 更多成员函数...

private:
size_t size_;
int* data_;
};

拷贝构造函数(复制语义)

当你使用拷贝构造函数创建一个新的Vector对象时,每个成员都会被复制到新对象中。对于动态分配的数组,会分配一块新的内存区域,并将原始数组的内容复制到这块新内存中。这保证了两个对象(原始对象和拷贝对象)之间的数据是完全独立的。

1
2
Vector v1(10); // 创建一个Vector对象v1
Vector v2 = v1; // 使用拷贝构造函数创建v2作为v1的副本

在上述代码中,v2v1的一个完整副本,v1v2各自拥有各自的内存区域。

移动构造函数(移动语义)

当你使用移动构造函数创建一个新的Vector对象时,原始对象的资源(在这里是动态分配的数组)会被“移动”到新对象中,而原始对象则被置于一个有效但“空”的状态(在这里是data_指针设置为nullptrsize_设置为0)。这避免了不必要的复制,用于临时对象或即将被销毁的对象特别有效,因为它大幅度减少了性能开销。

1
2
Vector v3(10); // 创建一个Vector对象v3
Vector v4 = std::move(v3); // 使用移动构造函数创建v4,"窃取" v3的资源

在上述代码中,v4现在拥有原本属于v3的内存,而v3现在不再拥有任何资源,因此不会在析构时释放v4正在使用的内存,从而避免了潜在的双重释放问题。

区别总结

  • 复制语义:在使用拷贝构造函数时,会创建对象的独立副本,包括复制动态分配的资源。
  • 移动语义:在使用移动构造函数时,会将资源的所有权从一个对象转移给另一个对象,通常这会留下一个处于安全但无资源的原始对象状态。