欢迎光临
我们一直在努力

C++ 移动语义、智能指针、STL容器** 的完整详解

以下是 C++ 移动语义、智能指针、STL容器 的完整详解,包含大量示例代码和最佳实践。


📘 第13章 拷贝控制与移动语义

13.1 拷贝控制概述

C++类可以定义以下5个特殊成员函数来控制对象的拷贝、移动、赋值和销毁:

操作函数名调用时机
拷贝构造 ClassName(const ClassName&) 按值传递、返回、初始化
拷贝赋值 ClassName& operator=(const ClassName&) 使用=赋值
移动构造 ClassName(ClassName&&) 从临时对象初始化
移动赋值 ClassName& operator=(ClassName&&) 从临时对象赋值
析构函数 ~ClassName() 对象销毁时

13.2 右值引用与移动语义

🔍 左值 vs 右值

int x = 10; // x 是左值(有名字,可取地址)
10; // 10 是右值(临时值,不可取地址)
x + 5; // 表达式结果是右值
std::move(x); // 将左值转换为右值引用

💡 右值引用 (&&)

右值引用允许我们绑定到临时对象,从而"窃取"其资源,避免深拷贝。

class String {
public:
// 普通构造函数
String(const char* s) {
data = new char[strlen(s) + 1];
strcpy(data, s);
cout << "普通构造" << endl;
}

// 拷贝构造函数(深拷贝)
String(const String& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
cout << "拷贝构造(深拷贝)" << endl;
}

// 移动构造函数(资源窃取)
String(String&& other) noexcept {
data = other.data; // 直接接管指针
other.data = nullptr; // 原对象置空
cout << "移动构造(资源窃取)" << endl;
}

// 拷贝赋值运算符
String& operator=(const String& other) {
if (this != &other) {
delete[] data;
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
cout << "拷贝赋值" << endl;
return *this;
}

// 移动赋值运算符
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
other.data = nullptr;
}
cout << "移动赋值" << endl;
return *this;
}

~String() {
delete[] data;
cout << "析构" << endl;
}

private:
char* data;
};

🧪 使用示例

String createString() {
return String("Hello"); // 返回临时对象
}

int main() {
String s1("World"); // 普通构造
String s2 = s1; // 拷贝构造
String s3 = createString(); // 移动构造(C++11起)
String s4 = std::move(s1); // 显式移动

s2 = s3; // 拷贝赋值
s3 = std::move(s4); // 移动赋值

return 0;
}

输出:

普通构造
拷贝构造(深拷贝)
普通构造
移动构造(资源窃取)
普通构造
移动构造(资源窃取)
拷贝赋值
移动赋值
析构
析构
析构
析构


13.3 完美转发(Perfect Forwarding)

🔍 问题背景

在模板函数中,我们需要保持参数的左值/右值属性传递给其他函数。

💡 std::forward 解决方案

// 错误:无法保持值类别
template<typename T>
void wrapper(T arg) {
process(arg); // arg 总是左值!
}

// 正确:使用万能引用 + std::forward
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // 保持原始值类别
}

🧩 完整示例

#include <iostream>
#include <utility>

void process(int& x) {
std::cout << "左值引用: " << x << std::endl;
}

void process(int&& x) {
std::cout << "右值引用: " << x << std::endl;
}

template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg));
}

int main() {
int x = 10;
wrapper(x); // 调用 process(int&)
wrapper(20); // 调用 process(int&&)
wrapper(std::move(x)); // 调用 process(int&&)
return 0;
}


13.4 规则_of_三/五/零

规则内容适用场景
三法则 需要定义析构、拷贝构造、拷贝赋值中的一个,通常需要全部定义 C++98/03
五法则 加上移动构造和移动赋值 C++11及以后
零法则 使用标准库容器和智能指针,无需自定义任何特殊成员函数 现代C++推荐

// ❌ 违反零法则
class Bad {
int* ptr;
public:
Bad() : ptr(new int(0)) {}
~Bad() { delete ptr; }
// 缺少拷贝/移动操作
};

// ✅ 遵循零法则
class Good {
std::unique_ptr<int> ptr; // 自动管理资源
public:
Good() : ptr(std::make_unique<int>(0)) {}
// 无需定义其他特殊成员函数
};


📘 第12章 智能指针

12.1 智能指针概述

智能指针所有权可拷贝可移动开销
unique_ptr 独占
shared_ptr 共享 引用计数
weak_ptr 弱引用 引用计数

12.2 unique_ptr 独占所有权

💡 基本用法

#include <memory>
#include <iostream>

class Resource {
public:
Resource() { std::cout << "Resource 构造\\n"; }
~Resource() { std::cout << "Resource 析构\\n"; }
void use() { std::cout << "使用资源\\n"; }
};

int main() {
// 创建方式1:make_unique(推荐)
std::unique_ptr<Resource> p1 = std::make_unique<Resource>();

// 创建方式2:直接构造
std::unique_ptr<Resource> p2(new Resource());

p1->use();
(*p1).use();

// 所有权转移
std::unique_ptr<Resource> p3 = std::move(p1);
// p1 现在为空,p3 拥有资源

// 重置
p3.reset(); // 释放资源
p3.reset(new Resource()); // 释放并获取新资源

// 释放但不删除
Resource* raw = p3.release();
delete raw; // 手动管理

return 0; // p2 自动析构
}

🧩 数组支持

// C++14起支持
std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
arr[0] = 42;

// 或使用数组特化
std::unique_ptr<int[], decltype(&delete[])> arr2(
new int[10], &delete[]
);

🧩 自定义删除器

auto deleter = [](FILE* f) {
if (f) fclose(f);
};

std::unique_ptr<FILE, decltype(deleter)> fp(
fopen("data.txt", "r"), deleter
);


12.3 shared_ptr 共享所有权

💡 基本用法

#include <memory>
#include <iostream>

int main() {
// 创建
std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::shared_ptr<int> p2 = p1; // 引用计数+1

std::cout << p1.use_count() << std::endl; // 2
std::cout << p2.use_count() << std::endl; // 2

// 检查唯一性
std::cout << p1.unique() << std::endl; // false

// 重置
p2.reset();
std::cout << p1.use_count() << std::endl; // 1

// 获取原始指针(谨慎使用)
int* raw = p1.get();

return 0; // 引用计数归零时自动删除
}

⚠️ 循环引用问题

class B; // 前向声明

class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A 析构\\n"; }
};

class B {
public:
std::shared_ptr<A> a_ptr; // ❌ 循环引用!
~B() { std::cout << "B 析构\\n"; }
};

int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();

a->b_ptr = b;
b->a_ptr = a; // 循环引用,内存泄漏!

return 0; // a 和 b 都不会析构
}

✅ 解决方案:weak_ptr

class B;

class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A 析构\\n"; }
};

class B {
public:
std::weak_ptr<A> a_ptr; // ✅ 弱引用,不增加计数
~B() { std::cout << "B 析构\\n"; }

void useA() {
if (auto sp = a_ptr.lock()) { // 提升为 shared_ptr
// 使用 sp
}
}
};


12.4 weak_ptr 弱引用

std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp; // 不增加引用计数

// 检查是否有效
if (!wp.expired()) {
std::shared_ptr<int> sp2 = wp.lock(); // 提升
std::cout << *sp2 << std::endl;
}

// 安全访问
if (auto sp2 = wp.lock()) {
std::cout << *sp2 << std::endl;
}


12.5 智能指针最佳实践

场景推荐
独占所有权 unique_ptr
共享所有权 shared_ptr
观察者/缓存 weak_ptr
工厂函数返回 unique_ptr 或 shared_ptr
类成员资源 unique_ptr(优先)
打破循环引用 weak_ptr

// ✅ 推荐:工厂函数
std::unique_ptr<Resource> createResource() {
return std::make_unique<Resource>();
}

// ✅ 推荐:类成员
class Widget {
std::unique_ptr<Impl> pImpl; // Pimpl惯用法
public:
Widget() : pImpl(std::make_unique<Impl>()) {}
};


📘 第9章 STL容器详解

9.1 容器分类

STL容器

┌──────────────┼──────────────┐
│ │ │
序列容器 关联容器 无序容器
│ │ │
┌───┼───┐ ┌───┼───┐ ┌───┼───┐
│ │ │ │ │ │ │ │ │
vector deque list map set unordered_map unordered_set


9.2 vector 动态数组

💡 基本操作

#include <vector>
#include <iostream>

int main() {
// 创建
std::vector<int> v1; // 空
std::vector<int> v2(10); // 10个元素,默认初始化
std::vector<int> v3(10, 42); // 10个元素,值为42
std::vector<int> v4 = {1, 2, 3, 4, 5}; // 初始化列表
std::vector<int> v5(v4); // 拷贝

// 容量操作
std::cout << v4.size() << std::endl; // 5
std::cout << v4.capacity() << std::endl // 可能>=5
v4.reserve(100); // 预留空间
v4.shrink_to_fit(); // 收缩容量

// 元素访问
v4[0] = 10; // 不检查边界
v4.at(0) = 20; // 检查边界,越界抛异常
v4.front(); // 第一个元素
v4.back(); // 最后一个元素

// 修改操作
v4.push_back(6); // 尾部添加
v4.pop_back(); // 尾部删除
v4.insert(v4.begin(), 0); // 插入
v4.erase(v4.begin()); // 删除
v4.clear(); // 清空

// 迭代器
for (auto it = v4.begin(); it != v4.end(); ++it) {
std::cout << *it << " ";
}

// 范围for
for (const auto& elem : v4) {
std::cout << elem << " ";
}

return 0;
}

⚠️ 迭代器失效规则

操作迭代器失效情况
push_back 如果超过capacity,全部失效
insert 插入点及之后的迭代器失效
erase 被删除元素及之后的迭代器失效
clear 全部失效

9.3 deque 双端队列

#include <deque>

std::deque<int> d = {1, 2, 3, 4, 5};

d.push_front(0); // 前端添加
d.pop_front(); // 前端删除
d.push_back(6); // 后端添加
d.pop_back(); // 后端删除

// 支持随机访问
int x = d[0];
int y = d.at(1);


9.4 list 双向链表

#include <list>

std::list<int> lst = {1, 2, 3, 4, 5};

lst.push_front(0);
lst.push_back(6);
lst.sort(); // 排序
lst.reverse(); // 反转
lst.merge(lst2); // 合并
lst.remove(3); // 删除值为3的元素
lst.unique(); // 删除相邻重复元素

// 只支持双向迭代器
for (auto it = lst.begin(); it != lst.end(); ++it) {
std::cout << *it << " ";
}


9.5 map 和 unordered_map

💡 map(有序,红黑树)

#include <map>

std::map<std::string, int> m;

// 插入
m["apple"] = 1;
m.insert({"banana", 2});
m.emplace("cherry", 3);

// 访问
int x = m["apple"]; // 不存在则创建
auto it = m.find("banana"); // 不存在返回end()
if (it != m.end()) {
std::cout << it->second;
}

// 遍历(按键排序)
for (const auto& [key, value] : m) {
std::cout << key << ": " << value << "\\n";
}

// 删除
m.erase("apple");
m.erase(m.begin());

💡 unordered_map(无序,哈希表)

#include <unordered_map>

std::unordered_map<std::string, int> um;

// 用法与 map 类似,但:
// – 平均 O(1) 查找(map 是 O(log n))
// – 元素无序
// – 需要可哈希的键类型

um.reserve(1000); // 预留桶数量
um.max_load_factor(0.75); // 设置负载因子

📊 性能对比

操作mapunordered_map
查找 O(log n) O(1) 平均
插入 O(log n) O(1) 平均
删除 O(log n) O(1) 平均
遍历 有序 无序
内存 较少 较多(哈希表)

9.6 set 和 unordered_set

#include <set>
#include <unordered_set>

std::set<int> s = {5, 2, 8, 2, 1}; // {1, 2, 5, 8} 自动去重排序
std::unordered_set<int> us = {5, 2, 8, 2, 1}; // 无序去重

s.count(2); // 检查是否存在(返回1或0)
s.find(5); // 查找
s.insert(10); // 插入
s.erase(2); // 删除


9.7 容器适配器

#include <stack>
#include <queue>

// 栈
std::stack<int> st;
st.push(1);
st.pop();
st.top();

// 队列
std::queue<int> q;
q.push(1);
q.pop();
q.front();
q.back();

// 优先队列
std::priority_queue<int> pq; // 大顶堆
std::priority_queue<int, std::vector<int>, std::greater<int>> pq2; // 小顶堆


9.8 容器选择指南

需求推荐容器
随机访问 + 尾部操作 vector
频繁头部插入删除 deque
频繁中间插入删除 list
按键排序 + 查找 map / set
快速查找(无需排序) unordered_map / unordered_set
栈结构 stack
队列结构 queue
优先队列 priority_queue

🎯 综合实战案例

案例1:实现一个简单的缓存系统

#include <memory>
#include <unordered_map>
#include <list>
#include <string>

template<typename K, typename V>
class LRUCache {
public:
explicit LRUCache(size_t capacity) : cap_(capacity) {}

V get(const K& key) {
auto it = cache_.find(key);
if (it == cache_.end()) {
return V{}; // 或抛异常
}
// 移动到前端(最近使用)
items_.splice(items_.begin(), items_, it->second);
return it->second->second;
}

void put(const K& key, const V& value) {
auto it = cache_.find(key);
if (it != cache_.end()) {
items_.splice(items_.begin(), items_, it->second);
it->second->second = value;
return;
}

if (items_.size() >= cap_) {
// 删除最久未使用
auto last = items_.back();
cache_.erase(last.first);
items_.pop_back();
}

items_.push_front({key, value});
cache_[key] = items_.begin();
}

private:
size_t cap_;
std::list<std::pair<K, V>> items_;
std::unordered_map<K, typename std::list<std::pair<K, V>>::iterator> cache_;
};

案例2:资源管理器(智能指针 + 移动语义)

#include <memory>
#include <vector>
#include <iostream>

class Database {
public:
Database(const std::string& conn) : connection_(conn) {
std::cout << "连接数据库: " << connection_ << "\\n";
}
~Database() {
std::cout << "断开数据库: " << connection_ << "\\n";
}

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

// 允许移动
Database(Database&&) noexcept = default;
Database& operator=(Database&&) noexcept = default;

private:
std::string connection_;
};

class ResourceManager {
public:
void addResource(std::unique_ptr<Database> db) {
resources_.push_back(std::move(db));
}

Database* getResource(size_t index) {
if (index < resources_.size()) {
return resources_[index].get();
}
return nullptr;
}

private:
std::vector<std::unique_ptr<Database>> resources_;
};


📚 总结对比表

移动语义关键概念

概念说明示例
左值引用 T& int& x = a;
右值引用 T&& int&& r = 10;
万能引用 T&&(模板中) template<T> void f(T&& x)
std::move 转为右值引用 std::move(x)
std::forward 完美转发 std::forward<T>(x)

智能指针选择

是否需要共享所有权?
├── 否 → unique_ptr
└── 是 → 是否需要打破循环引用?
├── 否 → shared_ptr
└── 是 → weak_ptr(配合 shared_ptr)

容器性能对比

容器随机访问头插/删中插/删尾插/删查找
vector O(1) O(n) O(n) O(1)* O(n)
deque O(1) O(1) O(n) O(1) O(n)
list O(n) O(1) O(1) O(1) O(n)
map O(n) O(log n)
unordered_map O(n) O(1)

*vector 尾插在capacity不足时需要扩容,摊还O(1)


如果你想深入了解某个特定主题(如自定义删除器、容器内存模型、STL算法等),或者需要更多实战案例,请告诉我!🚀

赞(0)
未经允许不得转载:171主机测评 » C++ 移动语义、智能指针、STL容器** 的完整详解
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址