# 默认构造、拷贝构造、移动构造以及赋值运算符重载
构造函数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
| auto ptr1 = std::make_shared<int>(10);
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); } return *this; }
~shared_ptr() { if (ctrl_ && --ctrl_->shared_count == 0) { ctrl_->deleter(); 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) {}
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); } return *this; }
~weak_ptr() { if (ctrl_ && --ctrl_->weak_count == 0 && ctrl_->shared_count == 0) { delete ctrl_; } }
shared_ptr<T> lock() const { if (expired()) return shared_ptr<T>{}; 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 2
| std::shared_ptr<int> p1 = std::make_shared<int>(42); std::shared_ptr<int> p2(p1);
|
- 拷贝赋值
智能指针1 2
| std::shared_ptr<int> p3; p3 = p1;
|
- 函数传参
智能指针1 2
| void foo(std::shared_ptr<int> p) { } foo(p1);
|
- 作为返回值返回
智能指针1 2 3 4
| std::shared_ptr<int> getPtr() { return p1; } auto p4 = getPtr();
|
现代编译器通常会进行 返回值优化(RVO) 或使用 移动语义,避免不必要的 +1/-1
- 放入容器(如 vector、list)
智能指针1 2
| std::vector<std::shared_ptr<int>> vec; vec.push_back(p1);
|
# -1
- 析构
智能指针1 2 3
| { std::shared_ptr<int> p = p1; }
|
- 调用 reset
智能指针1 2
| p1.reset(); p1.reset(nullptr);
|
- 被赋予新值
智能指针1 2 3 4
| std::shared_ptr<int> p2 = std::make_shared<int>(100); p1 = p2;
|
- 被赋予 nullptr 或空
智能指针
- 从容器中移除或 clear
智能指针
# 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_; T* capacity_; };
|
-
size() = end_ - begin_
-
capacity() = capacity_ - begin_
- 所有元素在
[begin_, end_) 范围内,内存连续。
实际标准库实现(如 libstdc++、libc++)可能用一个指针 + 两个整数(size/capacity),但逻辑等价。
# 二、内存分配与释放
- 使用 分配器(Allocator) 管理内存,默认是
std::allocator<T> 。
- 初始时
capacity() == 0 ,不分配内存(除非显式 reserve 或插入元素)。
- 当需要更多空间时,重新分配一块更大的连续内存,将旧元素移动或拷贝过去,再释放旧内存。
# 三、动态扩容机制(关键!)
# 1. 何时扩容?
当 size() == capacity() 且尝试插入新元素(如 push_back 、 insert )时,触发扩容。
# 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_; }
|
注意: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_back 或 erase 后 capacity() 不变,避免频繁 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
| #include<stdio.h> struct{ int x; char y; }s;
int main() { printf("%d\n",sizeof(s)); 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
| #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)); printf("%d\n",sizeof(x2)); printf("%d\n",sizeof(x3)); return 0; }
|
C 的 _Alignof 和 C++ 的 alignof 可以获得类型的对齐。