# 默认构造、拷贝构造、移动构造以及赋值运算符重载

构造函数
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
29
30
31
32
33
34
35
36
37
38
class MyClass {
private:
int* data;
public:
MyClass(): data(nullptr) {}
~MyClass() { delete data; }
MyClass(const MyClass& other): data(nullptr) {
if (other.data != nullptr) {
data = new int(*other.data);
}
}
MyClass(MyClass&& other) noexcept: data(nullptr) {
data = other.data;
other.data = nullptr;
}
// 拷贝赋值运算符
MyClass& operator=(const MyClass& other) {
if (this == &other) { // 自赋值检查
return *this;
}
delete data; // 释放当前资源
data = nullptr;
if (other.data != nullptr) {
data = new int(*other.data); // 深拷贝
}
return *this;
}
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this == &other) {
return *this;
}
delete data; // 释放当前资源
data = other.data; // 接管
other.data = nullptr; // 置空
return *this;
}
};

下面用动态数组示例补全拷贝 / 移动语义(遵循 Rule of Five):

数组
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class MyArray {
private:
int* data;
int size;
public:
MyArray(): data(nullptr), size(0) {}
~MyArray(){ delete[] data; }

MyArray(int sz): data(sz ? new int[sz] : nullptr), size(sz) {}

// 拷贝构造:深拷贝
MyArray(const MyArray& other): data(nullptr), size(other.size) {
if (other.data && other.size > 0) {
data = new int[other.size];
copy(other.data, other.data + other.size, data);
}
}

// 移动构造:接管资源
MyArray(MyArray&& other) noexcept: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}

// 拷贝赋值:先复制再交换(异常安全)
MyArray& operator=(const MyArray& other) {
if (this == &other) return *this;
int* newData = nullptr;
if (other.size > 0) {
newData = new int[other.size];
copy(other.data, other.data + other.size, newData);
}
delete[] data;
data = newData;
size = other.size;
return *this;
}

// 移动赋值:释放当前资源并接管
MyArray& operator=(MyArray&& other) noexcept {
if (this == &other) return *this;
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
return *this;
}

int getSize() const { return size; }
int* getData() const { return data; }
};

要点:

  • 对于拥有动态资源的类型,建议同时实现析构、拷贝构造、移动构造、拷贝赋值、移动赋值(Rule of Five)。
  • 拷贝要做深拷贝,移动要尽可能接管资源并置空原对象以避免双重释放。
  • 拷贝赋值实现中可以先分配新资源,再释放旧资源以提高异常安全性。

# 智能指针怎么用

https://zhuanlan.zhihu.com/p/1921579814285975670

智能指针
1
2
3
4
5
// 使用std::make_shared
auto ptr1 = std::make_shared<int>(10);

// 使用std::shared_ptr(new T(args...))
std::shared_ptr<int> ptr2(new int(10));
智能指针
1
2
3
4
5
6
7
8
std::shared_ptr<int> shared_ptr1 = std::make_shared<int>(10); 
std::weak_ptr<int> weak_ptr1 = shared_ptr1;
auto shared_ptr2 = weak_ptr1.lock();
if (shared_ptr2) {
std::cout << "Object is still alive: " << *shared_ptr2 << std::endl;
} else {
std::cout << "Object has been destroyed" << std::endl;
}

# 智能指针的实现原理

模板类

智能指针
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
template<typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
private:
T* ptr_;
Deleter deleter_;

public:
// 构造函数
explicit unique_ptr(T* p = nullptr) : ptr_(p) {}

// 禁止拷贝
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

// 允许移动
unique_ptr(unique_ptr&& other) noexcept
: ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
other.ptr_ = nullptr;
}

unique_ptr& operator=(unique_ptr&& other) noexcept {
if (this != &other) {
reset(other.release());
deleter_ = std::move(other.deleter_);
}
return *this;
}

// 析构函数
~unique_ptr() {
if (ptr_) deleter_(ptr_);
}

// 访问操作
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }

// 释放所有权
T* release() {
T* tmp = ptr_;
ptr_ = nullptr;
return tmp;
}

// 重置指针
void reset(T* p = nullptr) {
if (ptr_ != p) {
deleter_(ptr_);
ptr_ = p;
}
}

explicit operator bool() const { return ptr_ != nullptr; }
};

shared_ptr 有一个 control block,shared_ptr 的实现原理如下:

智能指针
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
template<typename T>
class shared_ptr {
private:
T* ptr_;
struct ControlBlock {
std::atomic<long> shared_count;
std::atomic<long> weak_count;
std::function<void()> deleter;
ControlBlock() : shared_count(1), weak_count(0) {}
};
ControlBlock* ctrl_;

public:
explicit shared_ptr(T* p = nullptr)
: ptr_(p), ctrl_(p ? new ControlBlock{} : nullptr) {}

// 拷贝构造
shared_ptr(const shared_ptr& other)
: ptr_(other.ptr_), ctrl_(other.ctrl_) {
if (ctrl_) ++ctrl_->shared_count;
}

// 赋值
shared_ptr& operator=(const shared_ptr& other) {
if (this != &other) {
this->~shared_ptr(); // 先析构当前对象
new (this) shared_ptr(other); // placement new 拷贝构造
}
return *this;
}

// 析构
~shared_ptr() {
if (ctrl_ && --ctrl_->shared_count == 0) {
ctrl_->deleter(); // 或 delete ptr_
if (ctrl_->weak_count == 0) delete ctrl_;
}
}

T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
long use_count() const { return ctrl_ ? ctrl_->shared_count.load() : 0; }
};

weak_ptr 的实现原理如下:

智能指针
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
template<typename T>
class weak_ptr {
private:
T* ptr_; // 指向实际对象(可能已析构!仅用于快速访问)
ControlBlock* ctrl_; // 指向共享的控制块

public:
// 默认构造
weak_ptr() : ptr_(nullptr), ctrl_(nullptr) {}

// 从 shared_ptr 构造
weak_ptr(const shared_ptr<T>& sp)
: ptr_(sp.ptr_), ctrl_(sp.ctrl_) {
if (ctrl_) ++ctrl_->weak_count;
}

// 拷贝构造
weak_prot(const weak_ptr& wp)
: ptr_(wp.ptr_), ctrl_(wp.ctrl_) {
if (ctrl_) ++ctrl_->weak_count;
}

// 赋值运算符
weak_ptr& operator=(const weak_ptr& wp) {
if (this != &wp) {
this->~weak_ptr(); // 先析构当前状态
new (this) weak_ptr(wp); // placement new 拷贝
}
return *this;
}

// 析构函数
~weak_ptr() {
if (ctrl_ && --ctrl_->weak_count == 0 && ctrl_->shared_count == 0) {
delete ctrl_; // 控制块不再被任何 shared_ptr 或 weak_ptr 引用
}
}

// 转换为 shared_ptr(如果对象还活着)
shared_ptr<T> lock() const {
if (expired()) return shared_ptr<T>{};
// 原子地尝试增加 shared_count(需线程安全)
long old_count = ctrl_->shared_count.load();
while (old_count != 0) {
if (ctrl_->shared_count.compare_exchange_weak(old_count, old_count + 1)) {
return shared_ptr<T>(ptr_, ctrl_); // 复用控制块
}
}
return shared_ptr<T>{}; // 对象已被销毁
}

// 检查对象是否已销毁
bool expired() const {
return ctrl_ == nullptr || ctrl_->shared_count == 0;
}

// 获取弱引用计数(调试用)
long use_count() const {
return ctrl_ ? ctrl_->shared_count.load() : 0;
}
};

多个 shared_ptr 对象可以共享同一个动态分配的对象,并通过一个共享的控制块(control block)来维护引用计数,当最后一个 shared_ptr 被销毁或重置时,自动释放所管理的对象。

# 1. 控制块(Control Block)

每个被 shared_ptr 管理的对象都会关联一个 控制块(control block),这个控制块通常包含以下信息:

  • 强引用计数(strong reference count):即有多少个 shared_ptr 正在共享该对象。
  • 弱引用计数(weak reference count):即有多少个 weak_ptr 指向该控制块(用于支持 weak_ptr )。
  • 被管理对象的指针(有时直接内嵌对象,如通过 make_shared )。
  • 删除器(deleter):可自定义的函数对象,用于释放资源(默认是 delete )。
  • 分配器(allocator):如果使用了自定义内存分配策略。

注意:控制块本身是堆上分配的(除非使用 make_shared ,此时对象和控制块可能分配在同一块内存中以提高效率)。

# 2. 引用计数的增减

  • 构造 / 拷贝 / 赋值
    • 当一个新的 shared_ptr 通过拷贝构造、移动构造或赋值操作指向同一对象时,强引用计数加 1
  • 析构 / 重置(reset)
    • 当一个 shared_ptr 被销毁或调用 reset() 时,强引用计数减 1
    • 如果强引用计数变为 0,则:
      • 调用删除器(deleter)释放被管理的对象;
      • 但控制块不会立即释放,因为可能还有 weak_ptr 存在(需等待弱引用计数也为 0 才释放控制块)。

# 3. 线程安全性

  • 引用计数的操作是原子的(通常使用 std::atomic 实现),因此多个线程同时拷贝或销毁 shared_ptr 是线程安全的。
  • 但注意:对被管理对象本身的访问不是线程安全的,需要用户自己加锁。

# shared_ptr 什么时候 + 1 什么时候 - 1

# +1

  1. 拷贝构造
智能指针
1
2
std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::shared_ptr<int> p2(p1); // +1 → 强引用计数变为 2
  1. 拷贝赋值
智能指针
1
2
std::shared_ptr<int> p3;
p3 = p1; // p3 原来为空,现在共享 p1 的对象 → +1
  1. 函数传参
智能指针
1
2
void foo(std::shared_ptr<int> p) { /* ... */ }
foo(p1); // 进入函数时拷贝 → +1;函数返回时析构 → -1
  1. 作为返回值返回
智能指针
1
2
3
4
std::shared_ptr<int> getPtr() {
return p1; // 返回时可能触发拷贝或移动(见下文)
}
auto p4 = getPtr(); // 若发生拷贝 → +1;若被优化为移动 → 不变

现代编译器通常会进行 返回值优化(RVO) 或使用 移动语义,避免不必要的 +1/-1

  1. 放入容器(如 vector、list)
智能指针
1
2
std::vector<std::shared_ptr<int>> vec;
vec.push_back(p1); // 拷贝进容器 → +1

# -1

  1. 析构
智能指针
1
2
3
{
std::shared_ptr<int> p = p1; // +1
} // p 析构 → -1
  1. 调用 reset
智能指针
1
2
p1.reset();        // 放弃当前对象 → -1
p1.reset(nullptr); // 同上
  1. 被赋予新值
智能指针
1
2
3
4
std::shared_ptr<int> p2 = std::make_shared<int>(100);
p1 = p2;
// 先对 p1 原来的对象 -1,
// 再对 p2 的对象 +1(因为 p1 现在共享 p2 的对象)
  1. 被赋予 nullptr 或空
智能指针
1
p1 = nullptr;  // 相当于 reset() → -1
  1. 从容器中移除或 clear
智能指针
1
vec.clear();  // 容器中每个 shared_ptr 析构 → 各自管理的对象引用计数 -1

# vector 底层

连续内存存储 + 动态扩容

# 一、基本结构

std::vector<T> 内部通常维护三个关键指针(或等效的成员):

1
2
3
4
5
6
7
template<typename T, typename Allocator = std::allocator<T>>
class vector {
private:
T* begin_; // 指向首元素
T* end_; // 指向最后一个元素的下一个位置(即 size 的边界)
T* capacity_; // 指向已分配内存的末尾(即 capacity 的边界)
};
  • size() = end_ - begin_
  • capacity() = capacity_ - begin_
  • 所有元素在 [begin_, end_) 范围内,内存连续。

实际标准库实现(如 libstdc++、libc++)可能用一个指针 + 两个整数(size/capacity),但逻辑等价。

# 二、内存分配与释放

  • 使用 分配器(Allocator) 管理内存,默认是 std::allocator<T>
  • 初始时 capacity() == 0 ,不分配内存(除非显式 reserve 或插入元素)。
  • 当需要更多空间时,重新分配一块更大的连续内存,将旧元素移动或拷贝过去,再释放旧内存。

# 三、动态扩容机制(关键!)

# 1. 何时扩容?

size() == capacity() 且尝试插入新元素(如 push_backinsert )时,触发扩容。

# 2. 扩容策略

  • 通常采用 倍增策略(geometric growth)
    • GCC (libstdc++):容量 × 2
    • MSVC:容量 × 1.5
    • 目的:保证 摊还时间复杂度 O (1)push_back

例如:初始容量 1 → 2 → 4 → 8 → 16 …

# 3. 扩容过程

std::vector 申请一块新的、更大的连续内存

  • 将旧内存中的所有元素 逐个移动(move)或复制(copy) 到新内存(优先使用 move 构造,若类型支持)。
  • 销毁旧内存中的对象。
  • 释放旧内存。
  • 更新内部指针(指向新内存)和 capacity ()。
    这个过程的时间复杂度是 O (n),其中 n 是当前元素数量。
1
2
3
4
5
6
7
8
9
10
11
12
13
void push_back(const T& value) {
if (size() == capacity()) {
size_t new_cap = (capacity() == 0) ? 1 : capacity() * 2;
T* new_mem = allocator.allocate(new_cap); // 分配新内存
uninitialized_move(begin_, end_, new_mem); // 移动旧元素
allocator.deallocate(begin_, capacity()); // 释放旧内存
begin_ = new_mem;
end_ = new_mem + size();
capacity_ = new_mem + new_cap;
}
construct(end_, value); // 在 end_ 处构造新元素
++end_;
}

注意:C++11 起优先使用 移动语义(move)而非拷贝,提升性能。

# 四、关键操作的时间复杂度

操作 时间复杂度 说明
operator[] , at() O(1) 随机访问
push_back() O (1) 摊还 偶尔 O (n) 扩容
pop_back() O(1) 仅析构最后一个元素,不释放内存
insert(pos, val) O(n) 需移动 pos 后所有元素
erase(pos) O(n) 同上
clear() O(n) 析构所有元素,但 不释放内存(capacity 不变)
shrink_to_fit() O(n) 请求释放多余内存(非强制,依赖实现)

# 五、内存管理特性

  • 内存连续:支持与 C 数组互操作(如 &vec[0]vec.data() )。
  • 不自动缩容pop_backerasecapacity() 不变,避免频繁 realloc。
  • 异常安全:现代实现通常提供 强异常安全保证(如 copy-and-swap 技术)。

# 内存对齐

结构体内存对齐,理论上,32 位系统下,int 占 4byte,char 占一个 byte,那么将它们放到一个结构体中应该占 4+1=5byte;但是实际上,通过运行程序得到的结果是 8 byte,这就是内存对齐所导致的

内存对齐
1
2
3
4
5
6
7
8
9
10
11
12
//32位系统
#include<stdio.h>
struct{
int x;
char y;
}s;

int main()
{
printf("%d\n",sizeof(s)); // 输出8
return 0;
}

尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的。它一般会以双字节,四字节,8 字节,16 字节甚至 32 字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度

每个特定平台上的编译器都有自己的默认 “对齐系数”(也叫对齐模数)。gcc 中默认 #pragma pack (4),可以通过预编译命令 #pragma pack (n),n = 1,2,4,8,16 来改变这一系数。

内存对齐需要遵循的规则:

(1) 结构体第一个成员的偏移量(offset)为 0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
(3) 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

内存对齐
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
//32位系统
#include<stdio.h>
struct
{
int i;
char c1;
char c2;
}x1;

struct{
char c1;
int i;
char c2;
}x2;

struct{
char c1;
char c2;
int i;
}x3;

int main()
{
printf("%d\n",sizeof(x1)); // 输出8, 4 2 2
printf("%d\n",sizeof(x2)); // 输出12, 4 4 4
printf("%d\n",sizeof(x3)); // 输出8, 2 2 4
return 0;
}

C 的 _Alignof 和 C++ 的 alignof 可以获得类型的对齐。

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

NoResponse WeChat Pay

WeChat Pay

NoResponse Alipay

Alipay

NoResponse PayPal

PayPal