dede做的网站打不开织梦调用wordpress
侯捷 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) {}// 其他成员函数...
};
对象在内存中的布局:
classDiagramclass 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 智能指针封装
classDiagramclass 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表达式与函数对象
classDiagramclass Lambda {-captured_variables+operator()}class Function {+virtual call()}Lambda --|> Functionnote for Lambda "编译器生成的闭包类型"
class Widget {vector<int> data;
public:void processData() {// 捕获this指针的lambdaauto 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洪流中守护浪漫——编译等待的须臾恰似调试时的顿悟,终成青春独有留白。