侯捷 C++ 课程学习笔记:深入理解C++内存管理与类对象构造全过程
侯捷 C++ 课程学习笔记:深入理解C++内存管理与类对象构造全过程
学习笔记目录
- 侯捷 C++ 课程学习笔记:深入理解C++内存管理与类对象构造全过程
- 一、学习背景与心得
- 1.1 为什么选择侯捷老师的课程
- 1.2 学习方法总结
- 1.3 C++学习路线图
- 二、内存管理专题深度解析
- 2.1 C++对象模型
- 2.2 内存分配过程
- 2.3 内存管理实战案例
- 三、类对象构造过程详解
- 3.1 构造函数调用顺序
- 3.2 深拷贝与浅拷贝
- 3.3 性能优化实践
- 3.4 RAII资源管理
- 3.5 完美转发
- 四、实际项目应用
- 4.1 智能指针封装
- 4.2 项目实践:线程安全的单例模式
- 4.3 线程池实现
- 4.4 高性能日志系统
- 五、现代C++特性应用
- 5.1 可变参数模板
- 5.2 Lambda表达式与函数对象
- 六、性能优化技巧
- 6.1 内存对齐与缓存优化
- 6.2 编译期计算
- 七、学习总结与心得
- 7.1 知识体系构建
- 7.2学习路线
- 7.3学习感悟
一、学习背景与心得
作为一名大学生,在学习C++的过程中,我一直在寻找能够真正理解这门语言本质的学习资源。很幸运地接触到了侯捷老师的C++系列课程,这些课程不仅帮助我建立了完整的C++知识体系,更重要的是让我理解了C++的设计理念和底层实现机制。
1.1 为什么选择侯捷老师的课程
- 系统性强:从内存模型到设计模式,层层递进
- 深入浅出:通过大量实例讲解抽象概念
- 实战导向:理论结合实践,重视代码实现
- 独特见解:对C++语言特性有深刻理解
- 经验丰富:结合实际工程经验讲解
1.2 学习方法总结
- 跟随课程编写代码,反复调试验证
- 绘制知识脑图,建立知识关联
- 通过实际项目实践,巩固所学内容
- 记录学习笔记,定期复习回顾
- 参与开源项目,实践所学知识
1.3 C++学习路线图
二、内存管理专题深度解析
2.1 C++对象模型
本节通过Complex类展示C++对象在内存中的布局方式。每个对象实例在内存中按成员声明顺序连续存储,构造函数通过初始化列表完成成员初始化,这是C++对象构造的基础模型。
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// 其他成员函数...
};
对象在内存中的布局:
classDiagram
class Complex {
-double real
-double imag
+Complex(double r, double i)
}
note for Complex "内存布局:\n| real (8字节) |\n| imag (8字节) |"
内存布局示意图展示了Complex类实例的内存结构。每个double类型占8字节,两个成员变量按声明顺序连续排列,总大小为16字节。构造函数初始化列表直接操作这些内存位置。
2.2 内存分配过程
C++内存分配分为静态和动态两种方式,理解它们的差异是掌握内存管理的关键。静态分配由编译器自动管理生命周期,动态分配需要开发者手动管理。
- 静态分配
Complex c1(1.0, 2.0); // 栈上分配
static Complex c2; // 静态存储区
c1在函数栈帧中分配,生命周期与作用域绑定。c2在程序静态存储区分配,生命周期持续到程序结束。两者都不需要手动释放。
- 动态分配
Complex* pc = new Complex(1.0, 2.0); // 堆上分配
// ...使用对象
delete pc; // 释放内存
new操作符在堆上分配内存并构造对象,返回指针需要手动管理。delete操作符先调用析构函数再释放内存,必须配对使用避免内存泄漏。
内存分配流程:
流程图揭示了new/delete的底层机制:内存分配与对象构造分离。operator new仅分配原始内存,构造函数初始化对象,这个分离机制是placement new等技术的基础。
2.3 内存管理实战案例
自定义内存池是优化频繁内存分配的有效方案。通过预分配内存块和链表管理,减少系统调用次数,提升内存分配效率。
// 自定义内存池
class MemoryPool {
private:
struct Block {
Block* next;
char data[1024]; // 实际的内存块
};
Block* freeList;
public:
MemoryPool() : freeList(nullptr) {
// 预分配一些内存块
expandPool();
}
void* allocate(size_t size) {
if (size > sizeof(Block::data)) return nullptr;
if (!freeList) expandPool();
Block* block = freeList;
freeList = freeList->next;
return block->data;
}
void deallocate(void* p) {
Block* block = reinterpret_cast<Block*>(
reinterpret_cast<char*>(p) - offsetof(Block, data)
);
block->next = freeList;
freeList = block;
}
private:
void expandPool() {
// 分配新的内存块链表
for (int i = 0; i < 10; ++i) {
Block* block = new Block;
block->next = freeList;
freeList = block;
}
}
};
内存池实现要点:
- 通过freeList链表管理空闲内存块
- 分配时从链表头部取内存块
- 释放时通过offsetof计算块头地址,将内存块插回链表
- 内存不足时批量扩展(expandPool)
- 每个内存块包含控制头(Block)和用户数据区(data)
三、类对象构造过程详解
3.1 构造函数调用顺序
派生类对象的构造遵循严格顺序:基类构造->成员构造->派生类构造体。理解这个顺序对正确初始化对象至关重要。
class Base {
int x;
public:
Base() : x(0) {
cout << "Base constructor\n";
}
};
class Derived : public Base {
int y;
public:
Derived() : y(0) {
cout << "Derived constructor\n";
}
};
构造过程:
构造顺序的实践意义:
- 基类构造函数中不要调用虚函数
- 成员变量按声明顺序初始化(而非初始化列表顺序)
- 派生类构造函数可以依赖基类已初始化的成员
3.2 深拷贝与浅拷贝
拷贝控制是C++类设计的核心问题。深拷贝保证对象独立性,移动语义(C++11)提升资源管理效率。
class String {
private:
char* str;
public:
// 构造函数
String(const char* s = nullptr) {
if (s) {
str = new char[strlen(s) + 1];
strcpy(str, s);
} else {
str = new char[1];
str[0] = '\0';
}
}
// 深拷贝构造函数
String(const String& other) {
str = new char[strlen(other.str) + 1];
strcpy(str, other.str);
}
// 移动构造函数 (C++11)
String(String&& other) noexcept {
str = other.str;
other.str = nullptr;
}
~String() {
delete[] str;
}
};
关键点分析:
- 默认拷贝构造函数执行浅拷贝,会导致双重释放
- 深拷贝通过new分配独立内存,适合需要副本的场景
- 移动构造转移资源所有权,避免不必要的拷贝
- noexcept声明保证移动操作不会抛出异常
- 析构函数需要安全处理空指针
3.3 性能优化实践
构造函数优化是提升C++性能的重要手段,主要方法包括使用初始化列表和利用返回值优化。
- 使用初始化列表
class Point {
int x, y;
public:
// 推荐:使用初始化列表
Point(int xx, int yy) : x(xx), y(yy) {}
// 不推荐:构造函数体内赋值
Point(int xx, int yy) {
x = xx;
y = yy;
}
};
- 返回值优化(RVO)
class Heavy {
// 大量数据成员
public:
Heavy() { /* 初始化 */ }
};
Heavy createHeavy() {
return Heavy(); // 编译器会优化掉不必要的拷贝
}
int main() {
Heavy h = createHeavy(); // 直接构造,无需拷贝
}
3.4 RAII资源管理
RAII(Resource Acquisition Is Initialization)是C++核心编程范式,通过对象生命周期管理资源。
template<typename Resource>
class ResourceGuard {
private:
Resource* resource;
public:
explicit ResourceGuard(Resource* r) : resource(r) {}
~ResourceGuard() {
if (resource) {
resource->release();
delete resource;
}
}
// 禁止拷贝
ResourceGuard(const ResourceGuard&) = delete;
ResourceGuard& operator=(const ResourceGuard&) = delete;
// 允许移动
ResourceGuard(ResourceGuard&& other) noexcept : resource(other.resource) {
other.resource = nullptr;
}
Resource* get() const { return resource; }
};
// 使用示例
class DatabaseConnection {
public:
void release() { /* 关闭数据库连接 */ }
};
void processData() {
ResourceGuard<DatabaseConnection> db(new DatabaseConnection());
// 使用数据库连接...
// 离开作用域时自动释放资源
}
RAII实现要点:
- 资源在构造函数中获取
- 析构函数保证资源释放
- 禁用拷贝防止重复释放
- 允许移动实现资源所有权转移
- 通过模板实现通用资源管理
3.5 完美转发
完美转发保持参数的值类别(左值/右值),是泛型编程的重要技术,常用于工厂函数和包装器。
template<typename T, typename... Args>
unique_ptr<T> make_unique(Args&&... args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
// 使用示例
class Widget {
public:
Widget(int x, string str) {}
};
auto w = make_unique<Widget>(42, "Hello");
关键要素:
- 通用引用(Args&&)保持参数类型
- std::forward有条件转换回原始值类别
- 避免参数传递过程中的不必要拷贝
- 与变参模板结合实现任意参数转发
- 是现代C++智能指针工厂函数的基础实现
四、实际项目应用
4.1 智能指针封装
classDiagram
class SmartPtr~T~ {
-T* ptr
+SmartPtr(T* p)
+~SmartPtr()
+operator*() T&
+operator->() T*
-SmartPtr(const SmartPtr&)
-operator=(const SmartPtr&)
}
note for SmartPtr "RAII原则:\n资源获取即初始化\n析构时自动释放"
template<typename T>
class SmartPtr {
private:
T* ptr;
public:
explicit SmartPtr(T* p = nullptr) : ptr(p) {}
~SmartPtr() {
delete ptr;
}
// 禁止拷贝
SmartPtr(const SmartPtr&) = delete;
SmartPtr& operator=(const SmartPtr&) = delete;
// 移动语义
SmartPtr(SmartPtr&& other) noexcept {
ptr = other.ptr;
other.ptr = nullptr;
}
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
};
4.2 项目实践:线程安全的单例模式
class Singleton {
private:
static std::mutex mutex_;
static std::atomic<Singleton*> instance_;
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton* getInstance() {
Singleton* tmp = instance_.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mutex_);
tmp = instance_.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);
instance_.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
};
std::mutex Singleton::mutex_;
std::atomic<Singleton*> Singleton::instance_{nullptr};
4.3 线程池实现
class ThreadPool {
private:
vector<thread> workers;
queue<function<void()>> tasks;
mutex queue_mutex;
condition_variable condition;
bool stop;
public:
ThreadPool(size_t threads) : stop(false) {
for(size_t i = 0; i < threads; ++i)
workers.emplace_back([this] {
while(true) {
function<void()> task;
{
unique_lock<mutex> lock(queue_mutex);
condition.wait(lock,
[this]{ return stop || !tasks.empty(); });
if(stop && tasks.empty())
return;
task = move(tasks.front());
tasks.pop();
}
task();
}
});
}
template<class F>
void enqueue(F&& f) {
{
unique_lock<mutex> lock(queue_mutex);
tasks.push(forward<F>(f));
}
condition.notify_one();
}
~ThreadPool() {
{
unique_lock<mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(auto &worker: workers)
worker.join();
}
};
// 使用示例
ThreadPool pool(4);
auto result = pool.enqueue([](int x) { return x * x; }, 42);
4.4 高性能日志系统
class Logger {
private:
mutex write_mutex;
ofstream log_file;
queue<string> message_queue;
condition_variable cv;
bool running;
thread writer_thread;
void writer_loop() {
while(running || !message_queue.empty()) {
unique_lock<mutex> lock(write_mutex);
cv.wait(lock, [this]{
return !message_queue.empty() || !running;
});
while(!message_queue.empty()) {
log_file << message_queue.front() << endl;
message_queue.pop();
}
}
}
public:
Logger(const string& filename)
: log_file(filename, ios::app)
, running(true) {
writer_thread = thread(&Logger::writer_loop, this);
}
void log(const string& message) {
lock_guard<mutex> lock(write_mutex);
message_queue.push(message);
cv.notify_one();
}
~Logger() {
running = false;
cv.notify_one();
if(writer_thread.joinable())
writer_thread.join();
}
};
五、现代C++特性应用
5.1 可变参数模板
template<typename T>
T sum(T t) {
return t;
}
template<typename T, typename... Args>
T sum(T first, Args... args) {
return first + sum(args...);
}
// 使用示例
int total = sum(1, 2, 3, 4, 5); // 15
5.2 Lambda表达式与函数对象
classDiagram
class Lambda {
-captured_variables
+operator()
}
class Function {
+virtual call()
}
Lambda --|> Function
note for Lambda "编译器生成的闭包类型"
class Widget {
vector<int> data;
public:
void processData() {
// 捕获this指针的lambda
auto printer = [this](int x) {
cout << "Processing: " << x << endl;
};
// 使用标准算法
for_each(data.begin(), data.end(), printer);
// 排序示例
sort(data.begin(), data.end(),
[](int a, int b) { return abs(a) < abs(b); });
}
};
六、性能优化技巧
6.1 内存对齐与缓存优化
// 不良示例
struct BadLayout {
char a; // 1字节
double b; // 8字节
char c; // 1字节
}; // 实际占用24字节
// 优化后
struct GoodLayout {
double b; // 8字节
char a; // 1字节
char c; // 1字节
// 6字节padding
}; // 实际占用16字节
6.2 编译期计算
template<unsigned n>
struct Factorial {
static constexpr unsigned value = n * Factorial<n-1>::value;
};
template<>
struct Factorial<0> {
static constexpr unsigned value = 1;
};
// 编译期计算阶乘
constexpr unsigned fact5 = Factorial<5>::value;
七、学习总结与心得
7.1 知识体系构建
7.2学习路线
7.3学习感悟
编程是思想的育苗,键盘声里逻辑抽枝。与指针博弈的深夜犹如星际迷航,当内存迷雾消散,指针已成新世界的通行证;调试化作侦探游戏,报错信息皆是解密线索;用代码积木构建项目,完成首个程序诞生时恍见思想破茧成蝶。习得不仅是语法规则,更是与机器的诗意对话,在01洪流中守护浪漫——编译等待的须臾恰似调试时的顿悟,终成青春独有留白。