25.手撕:Unique_ptr,控制权转移(移动语义)手撕:类继承,堆栈上分别代码实现多态
🔬 手撕:Unique_ptr与多态实现
📖 内容概览
本文将详细介绍两个重要的C++概念实现:
- 手撕Unique_ptr:实现一个简易版的智能指针,支持RAII和移动语义
- 堆栈上的多态实现:通过类继承和虚函数,在堆栈上实现多态效果
🎯 核心概念
🧩 Unique_ptr核心特性
Unique_ptr是C++11引入的智能指针,具有以下核心特性:
- 独占所有权:一个对象只能被一个Unique_ptr管理
- 自动释放:离开作用域时自动调用delete
- 移动语义:支持所有权转移,不支持拷贝
- RAII机制:资源获取即初始化
- 类型安全:编译时检查类型
🔄 移动语义
移动语义是C++11引入的重要特性,用于解决临时对象的拷贝开销问题:
- 右值引用:使用&&表示,可以绑定到临时对象
- 移动构造函数:将资源从一个对象转移到另一个对象
- 移动赋值运算符:将资源从一个对象转移到另一个已存在的对象
- std::move:将左值转换为右值引用,用于触发移动操作
📋 多态的实现条件
实现多态需要满足以下条件:
- 继承关系:存在基类和派生类
- 虚函数:基类中声明虚函数
- 重写:派生类中重写基类的虚函数
- 基类指针/引用:通过基类指针或引用调用虚函数
🛠️ 代码实现
🛠️ 手撕Unique_ptr
#include <iostream>#include <utility> // 用于std::swap// 自定义Unique_ptr实现template <typename T>class Unique_ptr {private: T* ptr_ = nullptr; // 管理的原始指针public: // 1. 默认构造函数 constexpr Unique_ptr() noexcept = default; // 2. 构造函数:接收原始指针 explicit Unique_ptr(T* p) noexcept : ptr_(p) { std::cout << "📦 Unique_ptr构造:管理指针 " << ptr_ << std::endl; } // 3. 移动构造函数 Unique_ptr(Unique_ptr&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; // 转移所有权后,原指针置空 std::cout << "🔄 Unique_ptr移动构造" << std::endl; // 4. 析构函数:自动释放资源 ~Unique_ptr() { if (ptr_) { std::cout << "🗑️ Unique_ptr析构:释放指针 " << ptr_ << std::endl; delete ptr_; ptr_ = nullptr; } // 5. 移动赋值运算符 Unique_ptr& operator=(Unique_ptr&& other) noexcept { if (this != &other) { // 释放当前资源 if (ptr_) { std::cout << "🗑️ 释放旧资源 " << ptr_ << std::endl; delete ptr_; } // 转移所有权 ptr_ = other.ptr_; other.ptr_ = nullptr; std::cout << "🔄 Unique_ptr移动赋值" << std::endl; return *this; // 6. 禁止拷贝构造和拷贝赋值 Unique_ptr(const Unique_ptr&) = delete; Unique_ptr& operator=(const Unique_ptr&) = delete; // 7. 重载->运算符 T* operator->() const noexcept { return ptr_; // 8. 重载*运算符 T& operator*() const noexcept { return *ptr_; // 9. 获取原始指针 T* get() const noexcept { // 10. 释放所有权 T* release() noexcept { T* temp = ptr_; ptr_ = nullptr; std::cout << "🔓 释放所有权:" << temp << std::endl; return temp; // 11. 重置指针 void reset(T* p = nullptr) noexcept { if (ptr_ != p) { std::cout << "🗑️ 重置时释放旧资源 " << ptr_ << std::endl; ptr_ = p; std::cout << "🔄 重置为新资源 " << ptr_ << std::endl; // 12. 交换资源 void swap(Unique_ptr& other) noexcept { std::swap(ptr_, other.ptr_); std::cout << "🔄 交换资源" << std::endl; // 13. 检查是否拥有资源 explicit operator bool() const noexcept { return ptr_ != nullptr;};// 辅助函数:交换两个Unique_ptrvoid swap(Unique_ptr<T>& lhs, Unique_ptr<T>& rhs) noexcept { lhs.swap(rhs);}// 测试类class Test { int id_; Test(int id) : id_(id) { std::cout << "👤 Test构造:id=" << id_ << std::endl;
~Test() { std::cout << "👋 Test析构:id=" << id_ << std::endl; void show() { std::cout << "🔍 Test::show() called, id=" << id_ << std::endl;// 测试Unique_ptrvoid testUniquePtr() { std::cout << "\n=== 测试Unique_ptr ===" << std::endl; // 1. 基本使用 { Unique_ptr<Test> up1(new Test(1)); up1->show(); // 使用->访问成员 (*up1).show(); // 使用*访问对象 // 2. 移动语义 Unique_ptr<Test> up2(new Test(2)); Unique_ptr<Test> up3 = std::move(up2); // 移动构造
if (!up2) { std::cout << "❌ up2已释放所有权" << std::endl; if (up3) { std::cout << "✅ up3拥有所有权" << std::endl; up3->show(); // 移动赋值 Unique_ptr<Test> up4; up4 = std::move(up3); if (up4) { up4->show(); // 3. release和reset Unique_ptr<Test> up5(new Test(5)); Test* raw = up5.release(); // 释放所有权 raw->show(); delete raw; // 手动释放 up5.reset(new Test(6)); // 重置资源 up5->show(); up5.reset(); // 释放资源 std::cout << "=== 测试结束 ===\n" << std::endl;int main() { testUniquePtr(); return 0;2. 堆栈上的多态实现
#include <iostream>#include <string>
// 基类class Base {private: int base_value_;public: // 构造函数 Base(int value) : base_value_(value) { std::cout << "🏗️ Base构造:value=" << base_value_ << std::endl; }
// 虚析构函数:确保派生类析构函数被调用 virtual ~Base() { std::cout << "💥 Base析构" << std::endl; }
// 虚函数:可被派生类重写 virtual void show() { std::cout << "🔍 Base::show() called, value=" << base_value_ << std::endl; }
// 普通成员函数:不可被重写 void info() { std::cout << "📋 Base::info() called" << std::endl; }};
// 派生类class Derived : public Base {private: int derived_value_;public: // 构造函数:调用基类构造函数 Derived(int base_val, int derived_val) : Base(base_val), derived_value_(derived_val) { std::cout << "🏗️ Derived构造:derived_value=" << derived_value_ << std::endl; }
// 析构函数 ~Derived() override { std::cout << "💥 Derived析构" << std::endl; }
// 重写基类的虚函数 void show() override { std::cout << "🔍 Derived::show() called, derived_value=" << derived_value_ << std::endl; }
// 派生类自己的成员函数 void derivedInfo() { std::cout << "📋 Derived::derivedInfo() called" << std::endl; }};
// 另一个派生类class Derived2 : public Base {private: std::string name_;public: Derived2(int base_val, const std::string& name) : Base(base_val), name_(name) { std::cout << "🏗️ Derived2构造:name=" << name_ << std::endl; }
~Derived2() override { std::cout << "💥 Derived2析构" << std::endl; }
void show() override { std::cout << "🔍 Derived2::show() called, name=" << name_ << std::endl; }};
// 测试多态void testPolymorphism() { std::cout << "\n=== 测试多态 ===" << std::endl;
// 1. 直接调用:静态绑定 std::cout << "\n1. 直接调用(静态绑定):" << std::endl; Base base1(100); Derived derived1(200, 300); base1.show(); // 调用Base::show() derived1.show(); // 调用Derived::show() derived1.Base::show(); // 显式调用基类的show()
// 2. 通过指针调用:动态绑定(多态) std::cout << "\n2. 通过指针调用(动态绑定):" << std::endl; Base* ptr_base = &base1; ptr_base->show(); // 调用Base::show() ptr_base = &derived1; ptr_base->show(); // 调用Derived::show()(多态) ptr_base->info(); // 调用Base::info()(非虚函数,静态绑定) // ptr_base->derivedInfo(); // 错误:基类指针不能访问派生类特有成员
// 3. 通过引用调用:动态绑定(多态) std::cout << "\n3. 通过引用调用(动态绑定):" << std::endl; Base& ref_base = derived1; ref_base.show(); // 调用Derived::show()(多态)
// 4. 多个派生类的多态 std::cout << "\n4. 多个派生类的多态:" << std::endl; Derived2 derived2(400, "Test"); Base* ptrs[] = {&base1, &derived1, &derived2}; for (auto ptr : ptrs) { ptr->show(); // 分别调用不同类的show()方法 }
// 5. 堆栈对象的生命周期 std::cout << "\n5. 堆栈对象的生命周期:" << std::endl;}
int main() { testPolymorphism(); return 0;}📌 技术分析
1. Unique_ptr实现要点
- 模板设计:使用模板支持任意类型
- 独占所有权:禁止拷贝构造和拷贝赋值
- 移动语义:实现移动构造和移动赋值
- RAII机制:析构函数自动释放资源
- 类型安全:编译时检查类型
- 运算符重载:支持->、*、bool等运算符
- 辅助函数:release、reset、swap等
2. 移动语义的优势
- 减少拷贝开销:直接转移资源,不产生临时对象
- 提高性能:对于大对象或频繁创建销毁的对象效果显著
- 避免资源泄漏:通过RAII机制确保资源释放
- 支持不可拷贝对象的转移:如文件描述符、网络连接等
3. 多态的实现机制
- 虚函数表(vtable):每个类维护一个虚函数表,存储虚函数地址
- 虚函数指针(vptr):每个对象包含一个指向虚函数表的指针
- 动态绑定:运行时通过虚函数指针查找正确的虚函数
- 静态绑定:编译时确定调用哪个函数
⚠️ 注意事项
1. Unique_ptr的使用注意事项
- 不要手动释放资源:Unique_ptr会自动释放,手动释放会导致双重释放
- 不要共享所有权:Unique_ptr不支持拷贝,只能独占所有权
- 注意悬空指针:release()后,需要手动管理资源
- 避免循环引用:对于复杂数据结构,考虑使用weak_ptr
- 数组支持:需要专门的数组版本或使用std::unique_ptr<T[]>
2. 移动语义的注意事项
- 右值引用的生命周期:右值引用绑定的临时对象生命周期延长到引用的生命周期
- 移动后的对象状态:移动后的对象处于有效但未定义的状态,不应再使用
- std::move的使用:只对不再使用的对象使用std::move
- 移动构造函数的 noexcept:移动构造函数应标记为noexcept,否则可能影响容器性能
3. 多态的注意事项
- 虚析构函数:基类必须定义虚析构函数,否则会导致内存泄漏
- override关键字:使用override关键字明确表示重写,避免错误
- final关键字:防止类被继承或虚函数被重写
- 切片问题:避免将派生类对象赋值给基类对象,会丢失派生类特有成员
- 智能指针与多态:使用智能指针管理多态对象,确保正确释放
📋 总结
1. Unique_ptr的实现总结
- 成功实现了一个功能完整的Unique_ptr,支持:
- 基本的资源管理
- 移动语义
- RAII机制
- 各种辅助函数
- 运算符重载
- Unique_ptr是一种高效、安全的智能指针,适用于需要独占所有权的场景
2. 移动语义的总结
- 移动语义解决了临时对象的拷贝开销问题
- 移动构造函数和移动赋值运算符是实现移动语义的核心
- std::move用于触发移动操作
- 移动后的对象不应再被使用
3. 多态的总结
- 多态是面向对象编程的核心特性
- 实现多态需要继承、虚函数和基类指针/引用
- 虚函数表和虚函数指针是多态的实现机制
- 基类必须定义虚析构函数 通过手动实现Unique_ptr和堆栈上的多态,可以深入理解C++的核心概念和实现机制,提高对C++语言的掌握程度。这些实现展示了C++的强大功能和灵活性,同时也提醒我们在使用这些特性时需要注意相关的最佳实践和注意事项。