More Effective C++ 35个改善编程与设计的有效方法笔记与心得 5

五. 技术

条款25: 将 constructor 和 non-member functions虚化

请记住

1. 利用重载技术(overload)避免隐式类型转换(implicit type conversions)

‌‌‌‌  重载技术是指在同一个作用域中声明多个同名函数,但这些同名函数必须具有不同的参数列表。这样,编译器就可以根据实参的类型来选择适当的函数。

‌‌‌‌  隐式类型转换是指在使用不同类型的变量时,编译器自动将一种类型转换为另一种类型,这有时会导致不期望的行为。例如,如果一个函数接受 int 类型参数,但是你传递了一个 double 类型的实参,编译器会自动将 double 转换为 int

‌‌‌‌  为了避免这种隐式类型转换,你可以利用函数重载来显式地处理不同类型的参数。例如:

#include <iostream>void print(int x) {std::cout << "Integer: " << x << std::endl;
}void print(double x) {std::cout << "Double: " << x << std::endl;
}int main() {int i = 10;double d = 5.5;print(i); // 选择 void print(int x)print(d); // 选择 void print(double x)return 0;
}

‌‌‌‌  在这个例子中,通过重载 print 函数来处理 int 和 double 类型的参数,避免了隐式类型转换带来的潜在问题。

2. 将 constructor 和 non-member functions虚化(Virtualizing Constructors and Non-Member Functions)

‌‌‌‌  在C++中,**虚函数(virtual function)是一种可以在基类中声明,在派生类中重新定义的函数。虚函数的主要作用是实现多态,即在运行时根据对象的实际类型调用相应的函数。

‌‌‌‌  构造函数(constructor)通常不能是虚函数,因为在调用构造函数期间,对象还没有完全构造完成,虚函数机制无法正常工作。然而,你可以通过一些设计模式来模拟虚构造函数的行为。例如可以使用工厂模式(Factory Pattern)**:

class Base {
public:virtual ~Base() {}virtual Base* clone() const = 0; // 模拟虚构造函数
};class Derived : public Base {
public:virtual Derived* clone() const override {return new Derived(*this);}
};void example() {Base* obj = new Derived();Base* copy = obj->clone(); // 调用派生类的 "虚构造函数"delete obj;delete copy;
}

‌‌‌‌  在这个例子中,通过返回一个克隆的对象来实现类似虚构造函数的效果。

‌‌‌‌  **非成员函数(non-member functions)不能直接是虚函数,因为虚函数必须是类的成员函数。然而,可以将非成员函数包裹在类中,使其成为静态成员函数,然后通过类来管理这些函数的多态性。

‌‌‌‌  总结来说,这些技巧虽然复杂,但它们发挥出具体类型和对象多态性的作用,使得代码更灵活和可扩展。理解并应用这些概念会显著提升你的C++编程能力。

条款26: 限制某个class所能产生的对象数量

请记住
‌‌‌‌  限制某个类所能产生的对象数量是一种设计模式,通常用于确保在一个应用程序中只存在这个类的有限数量的实例。这个概念通常用于实现单例模式(Singleton Pattern),但它也可以推广到产生多个有限数量实例的情景。

‌‌‌‌  以下是实现这种模式的几种常见方法:

1. 单例模式

‌‌‌‌  单例模式确保一个类只有一个实例,并提供一个全局访问点来访问该实例。以下是一个简单的单例模式的实现:

class Singleton {
private:static Singleton* instance;Singleton() {} // 私有构造函数public:static Singleton* getInstance() {if (instance == nullptr) {instance = new Singleton();}return instance;}// 禁止拷贝构造和赋值操作Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
};// 静态成员初始化
Singleton* Singleton::instance = nullptr;int main() {Singleton* s1 = Singleton::getInstance();Singleton* s2 = Singleton::getInstance();// s1 和 s2 指向同一实例return 0;
}

2. 限数量模式

‌‌‌‌  如果需要限制某个类所能产生的对象数量为一个以上的特定值(例如,最多3个实例),可以使用一个静态计数器来跟踪对象的数量:

class LimitedInstances {
private:static const int maxInstances = 3;static int instanceCount;LimitedInstances() { instanceCount++; }public:~LimitedInstances() { instanceCount--; }static LimitedInstances* createInstance() {if (instanceCount < maxInstances) {return new LimitedInstances();} else {return nullptr; // 或者抛出异常, 或其他处理方式}}// 禁止拷贝构造和赋值操作LimitedInstances(const LimitedInstances&) = delete;LimitedInstances& operator=(const LimitedInstances&) = delete;
};// 初始化静态成员
int LimitedInstances::instanceCount = 0;int main() {LimitedInstances* inst1 = LimitedInstances::createInstance();LimitedInstances* inst2 = LimitedInstances::createInstance();LimitedInstances* inst3 = LimitedInstances::createInstance();LimitedInstances* inst4 = LimitedInstances::createInstance(); // 应返回 nullptr 或进行其他处理// 这里inst4应为 nullptr,因为已经创建了3个实例delete inst1;delete inst2;delete inst3;return 0;
}

‌‌‌‌  在这个例子中,LimitedInstances 类限制了最多可以创建3个实例。如果尝试超过这个数量,再调用 createInstance 将返回 nullptr

理解和应用背景

‌‌‌‌  限制类实例的数量的需求通常出现在以下情况:

  1. 资源有限:硬件资源或服务资源有限,只能允许特定数量的对象。
  2. 逻辑约束:业务逻辑要求只能存在特定数量的对象实例。
  3. 管理对象的生命周期:为了便于管理对象的创建和销毁,避免内存泄露或资源浪费。

‌‌‌‌  总结来说,限制一个类所能创建的对象数量是一个设计决策,通常用于控制资源的分配、维持系统的稳定性和确保业务逻辑正确执行。通过使用静态成员变量和合适的逻辑控制,可以实现对类实例数量的限制。

条款27: 要求(或禁止)对象产生于heap之中

请记住
‌‌‌‌  要求或禁止对象产生于堆(heap)之中是指控制对象的内存分配方式,即希望对象只能在堆上分配(通过 new 操作符)或者希望对象只能在栈上分配(自动变量)。这种需求出现在需要对对象的生命周期进行更精确的管理或优化性能的场景中。

要求对象只能产生于堆中

‌‌‌‌  要强制一个对象只能在堆上分配,可以将其构造函数声明为私有或受保护,并提供一个公有的静态工厂方法来创建对象。这样就只能通过这个工厂方法来创建对象,而无法直接在栈上分配。

class HeapOnly {
private:HeapOnly() {} // 私有构造函数防止栈分配public:// 使用静态工厂方法创建对象实例static HeapOnly* createInstance() {return new HeapOnly();}// 提供一个删除方法,确保对象可以被正确销毁void destroy() {delete this;}
};int main() {// HeapOnly obj; // 这行代码会编译错误,因为构造函数是私有的// 只能通过工厂方法在堆上创建HeapOnly* obj = HeapOnly::createInstance();// 使用对象// ...// 销毁对象obj->destroy();return 0;
}

‌‌‌‌  在这个例子中,由于构造函数是私有的,用户不能直接在栈上创建 HeapOnly 的实例。必须通过 createInstance 方法来在堆上创建对象。

禁止对象产生于堆中

‌‌‌‌  要强制一个对象只能在栈上分配,可以声明其 operator new 为私有或删除,以禁止在堆上分配。

class StackOnly {
private:// 删除堆分配操作符new和new[]void* operator new(size_t) = delete;void* operator new[](size_t) = delete;public:StackOnly() {}~StackOnly() {}
};int main() {StackOnly obj; // 正确:栈上分配// StackOnly* pObj = new StackOnly(); // 编译错误:删除的操作符// StackOnly* pArray = new StackOnly[10]; // 编译错误:删除的操作符return 0;
}

‌‌‌‌  在这个例子中,通过将 operator new 和 operator new[] 删除,禁止对象在堆上分配。这样,尝试在堆上创建 StackOnly 对象会导致编译错误。

理解和应用背景

‌‌‌‌  控制对象是否在栈上或堆上分配可以带来一些实际的好处:

  1. 生命周期管理:栈上分配的对象具有自动生命周期管理,出了作用域自动销毁,无需手动管理内存,可以避免内存泄露。
  2. 性能:栈分配速度通常比堆分配更快,因为栈是连续的内存空间,分配和释放开销较小。
  3. 设计约束:某些设计模式或API可能要求对象只能以特定方式分配,以确保功能或逻辑的正确性。

总结

‌‌‌‌  通过控制类的构造函数和 operator new,可以强制对象在栈上或堆上分配。这种技巧在需要精确管理对象生命周期和优化性能时非常有用。重要的是,设计这种控制时要结合具体需求和上下文,确保代码的可维护性和清晰性。

条款28: Smart Pointers(智能指针)

请记住

  1. Smart Pointers的构造、赋值、析构
  2. 实现Dereferencing Operators(解引用操作符)
  3. 测试Smart Pointers是否为NULL
  4. 将Smart Pointers转换为Dumb Pointers
  5. Smart Pointers和“与继承有关的”类型转换
  6. Smart Pointers 与 const

解释
‌‌‌‌  智能指针(Smart Pointers)是C++中的一种高级内存管理工具,它们通过封装裸指针来自动管理动态分配的内存,减少内存泄漏和其他与内存管理相关的问题。智能指针在标准库 <memory> 中定义,主要有以下几种类型:

  1. std::unique_ptr:独占所有权的智能指针。
  2. std::shared_ptr:共享所有权的智能指针。
  3. std::weak_ptr:非拥有型指针,配合 std::shared_ptr 一起使用。
  4. std::auto_ptr:已废弃,取而代之的是 std::unique_ptr

1. std::unique_ptr

‌‌‌‌  std::unique_ptr 表示独占所有权。一个对象只能有一个 std::unique_ptr 拥有,不能被复制,但可以转移所有权。

#include <iostream>
#include <memory>class Sample {
public:Sample() { std::cout << "Sample constructed.\n"; }~Sample() { std::cout << "Sample destructed.\n"; }void show() { std::cout << "Hello from Sample.\n"; }
};void unique_ptr_demo() {std::unique_ptr<Sample> ptr1 = std::make_unique<Sample>();ptr1->show();// std::unique_ptr<Sample> ptr2 = ptr1;  // 错误:不能复制std::unique_ptrstd::unique_ptr<Sample> ptr2 = std::move(ptr1); // 移动所有权if (!ptr1) {std::cout << "ptr1 is now null.\n";}
}int main() {unique_ptr_demo();return 0;
}

‌‌‌‌  当 ptr2 超出作用域时,Sample 对象会自动被销毁。

2. std::shared_ptr

‌‌‌‌  std::shared_ptr 表示共享所有权。多个 std::shared_ptr 可以共享同一个对象,该对象会在最后一个 std::shared_ptr 被销毁时自动释放。

#include <iostream>
#include <memory>class Sample {
public:Sample() { std::cout << "Sample constructed.\n"; }~Sample() { std::cout << "Sample destructed.\n"; }void show() { std::cout << "Hello from Sample.\n"; }
};void shared_ptr_demo() {std::shared_ptr<Sample> ptr1 = std::make_shared<Sample>();{std::shared_ptr<Sample> ptr2 = ptr1; // 共享所有权ptr2->show();std::cout << "ptr1 use count: " << ptr1.use_count() << "\n";std::cout << "ptr2 use count: " << ptr2.use_count() << "\n";} // ptr2超出作用域并被销毁std::cout << "ptr1 use count after ptr2 is out of scope: " << ptr1.use_count() << "\n";
}int main() {shared_ptr_demo();return 0;
}

‌‌‌‌  当 ptr1 和 ptr2 都超出作用域时,Sample 对象会被销毁。

3. std::weak_ptr

‌‌‌‌  std::weak_ptr 不拥有对象,只是一个对 std::shared_ptr 的弱引用。它常常用于打破循环引用,防止内存泄漏。

#include <iostream>
#include <memory>class Sample {
public:Sample() { std::cout << "Sample constructed.\n"; }~Sample() { std::cout << "Sample destructed.\n"; }
};void weak_ptr_demo() {std::shared_ptr<Sample> sharedPtr = std::make_shared<Sample>();std::weak_ptr<Sample> weakPtr = sharedPtr; // weakPtr 再也不是所有者std::cout << "sharedPtr use count: " << sharedPtr.use_count() << "\n";std::cout << "weakPtr use count: " << weakPtr.use_count() << "\n";  // 不增加计数if (auto spt = weakPtr.lock()) { // 尝试提升 weak_ptr 到 shared_ptrstd::cout << "Object is still alive.\n";} else {std::cout << "Object has been destroyed.\n";}
}int main() {weak_ptr_demo();return 0;
}

‌‌‌‌  在这个例子中,weakPtr 不会增加 sharedPtr 的引用计数,因此不会影响对象的生命周期。

4. std::auto_ptr(已废弃)

‌‌‌‌  std::auto_ptr 在C++11之后已经被废弃,建议使用 std::unique_ptr 代替。老版本的 std::auto_ptr 存在一些不安全的行为,例如复制会导致所有权转移,这可能会引发意外行为。

总结

‌‌‌‌  智能指针极大地简化了内存管理,通过RAII(Resource Acquisition Is Initialization)原则自动释放资源,避免了内存泄漏和悬挂指针等问题。使用智能指针时,需了解它们的特性与用法,选择最适合当前场景的智能指针类型可以有效地帮助管理动态内存。

‌‌‌‌  智能指针的构造、赋值和析构是C++内存管理的重要组成部分,了解它们的具体用法和机制可以帮助你更好地管理资源。下面我们详细介绍 std::unique_ptr 和 std::shared_ptr 的构造、赋值和析构。

std::unique_ptr

构造
  1. 默认构造:创建一个空的 unique_ptr
  2. 指针构造:将一个裸指针(raw pointer)封装进 unique_ptr
  3. 移动构造:从另一个 unique_ptr 移动构造,转移所有权。
#include <iostream>
#include <memory>class Sample {
public:Sample() { std::cout << "Sample constructed.\n"; }~Sample() { std::cout << "Sample destructed.\n"; }
};void unique_ptr_construction() {std::unique_ptr<Sample> ptr1; // 默认构造std::unique_ptr<Sample> ptr2(new Sample()); // 指针构造std::unique_ptr<Sample> ptr3 = std::make_unique<Sample>(); // 推荐的工厂方法// 移动构造std::unique_ptr<Sample> ptr4 = std::move(ptr2);
}int main() {unique_ptr_construction();return 0;
}
赋值
  1. 移动赋值:转移所有权。
  2. 通过 reset 重新分配:可以将 unique_ptr 重新指向一个新的对象。
void unique_ptr_assignment() {std::unique_ptr<Sample> ptr1 = std::make_unique<Sample>();std::unique_ptr<Sample> ptr2;// 移动赋值ptr2 = std::move(ptr1);// 重新分配ptr2.reset(new Sample());
}
析构

‌‌‌‌  当 unique_ptr 超出作用域或显式析构时,它管理的资源会被自动释放。

void unique_ptr_destruction() {std::unique_ptr<Sample> ptr = std::make_unique<Sample>();// 当函数返回时, Sample 对象会被析构
}

std::shared_ptr

构造
  1. 默认构造:创建一个空的 shared_ptr
  2. 指针构造:将一个裸指针封装进 shared_ptr
  3. 工厂方法:使用 std::make_shared 更加高效和安全。
  4. 复制构造:增加引用计数。
  5. 移动构造:从另一个 shared_ptr 移动构造。
void shared_ptr_construction() {std::shared_ptr<Sample> ptr1; // 默认构造std::shared_ptr<Sample> ptr2(new Sample()); // 指针构造std::shared_ptr<Sample> ptr3 = std::make_shared<Sample>(); // 推荐的工厂方法// 复制构造std::shared_ptr<Sample> ptr4 = ptr3;// 移动构造std::shared_ptr<Sample> ptr5 = std::move(ptr2);
}
赋值
  1. 复制赋值:增加引用计数。
  2. 移动赋值:转移所有权。
  3. 通过 reset 重新分配:可以将 shared_ptr 重新指向一个新的对象。
void shared_ptr_assignment() {std::shared_ptr<Sample> ptr1 = std::make_shared<Sample>();std::shared_ptr<Sample> ptr2;// 复制赋值ptr2 = ptr1;// 移动赋值std::shared_ptr<Sample> ptr3;ptr3 = std::move(ptr1);// 重新分配ptr2.reset(new Sample());
}
析构

‌‌‌‌  当最后一个 shared_ptr 超出作用域或显式析构时,它管理的资源会被自动释放。

void shared_ptr_destruction() {std::shared_ptr<Sample> ptr = std::make_shared<Sample>();// 当最后一个shared_ptr (这里是ptr) 被销毁时, Sample 对象会被析构
}

std::weak_ptr

构造
  1. 默认构造:创建一个空的 weak_ptr
  2. 从 shared_ptr 构造:弱引用一个 shared_ptr
void weak_ptr_construction() {std::shared_ptr<Sample> sharedPtr = std::make_shared<Sample>();std::weak_ptr<Sample> weakPtr1; // 默认构造std::weak_ptr<Sample> weakPtr2(sharedPtr); // 从shared_ptr构造
}
复制和移动赋值

‌‌‌‌  weak_ptr 也支持复制和移动赋值,不同于 shared_ptr,它们不会影响引用计数。

void weak_ptr_assignment() {std::shared_ptr<Sample> sharedPtr = std::make_shared<Sample>();std::weak_ptr<Sample> weakPtr1 = sharedPtr;std::weak_ptr<Sample> weakPtr2 = weakPtr1; // 复制赋值std::weak_ptr<Sample> weakPtr3 = std::move(weakPtr2); // 移动赋值
}
析构

‌‌‌‌  weak_ptr 的析构不会影响所引用对象的生命周期。它通常用于打破 shared_ptr 之间的循环引用。

总结

  • unique_ptr:独占所有权,不能复制,只能移动。
  • shared_ptr:共享所有权,可以复制和移动,通过引用计数管理对象生命周期。
  • weak_ptr:不拥有对象,只是一个对 shared_ptr 的弱引用,用于打破循环引用。

‌‌‌‌  智能指针通过控制对象的生命周期,帮助程序员避免内存泄漏和悬挂指针问题,是现代C++编程中必备的工具之一。

‌‌‌‌  智能指针(smart pointer)是一种用于管理动态内存的类,它提供了对普通指针的功能增强。智能指针的一个关键特性是支持解引用操作符*->),以便像使用普通指针一样访问对象。下面是如何实现一个基本的智能指针类,并支持解引用操作符的示例:

#include <iostream>template <typename T>
class SmartPointer {
private:T* ptr;public:// Constructorexplicit SmartPointer(T* p = nullptr) : ptr(p) {}// Destructor~SmartPointer() {delete ptr;}// Overload dereference operator *T& operator*() {return *ptr;}// Overload member access operator ->T* operator->() {return ptr;}// Copy constructorSmartPointer(const SmartPointer& other) {ptr = new T(*(other.ptr));}// Copy assignment operatorSmartPointer& operator=(const SmartPointer& other) {if (this != &other) {delete ptr;ptr = new T(*(other.ptr));}return *this;}// Move constructorSmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {other.ptr = nullptr;}// Move assignment operatorSmartPointer& operator=(SmartPointer&& other) noexcept {if (this != &other) {delete ptr;ptr = other.ptr;other.ptr = nullptr;}return *this;}// Disable copy and move semantics for simplicitySmartPointer(const SmartPointer&) = delete;SmartPointer& operator=(const SmartPointer&) = delete;
};class Test {
public:void show() {std::cout << "Test::show() called" << std::endl;}
};int main() {SmartPointer<Test> sp(new Test());sp->show();   // Use -> operator(*sp).show(); // Use * operatorreturn 0;
}

解释:

  1. 构造函数和析构函数

    • SmartPointer(T* p = nullptr) : ptr(p) {}: 构造函数接受一个指向类型 T 的指针,并将其存储在智能指针的私有成员变量 ptr 中。
    • ~SmartPointer() { delete ptr; }: 析构函数在智能指针销毁时释放内存。
  2. 解引用操作符

    • T& operator*() { return *ptr; }: 重载解引用操作符 *,返回指向的对象的引用。
    • T* operator->() { return ptr; }: 重载成员访问操作符 ->,返回指针以便访问对象的成员。
  3. 拷贝和移动语义

    • 拷贝构造函数和赋值操作符被删除以简化实现(但可以根据需求添加)。
    • 移动构造函数和移动赋值操作符用于转移所有权。

使用示例:

‌‌‌‌  在 main 函数中创建了一个 SmartPointer 实例指向 Test 对象,并通过 ->* 操作符调用了 Test 对象的 show 方法。

‌‌‌‌  这样实现的智能指针类可以在很多情况下替代普通指针,提供更安全的内存管理。

‌‌‌‌  为了测试智能指针是否为 NULL,我们可以重载布尔类型转换运算符。这使得智能指针在布尔上下文中(如条件语句中)可以像普通指针一样使用。下面是修改后的 SmartPointer 类,包含检查是否为 NULL 的功能:

#include <iostream>template <typename T>
class SmartPointer {
private:T* ptr;public:// Constructorexplicit SmartPointer(T* p = nullptr) : ptr(p) {}// Destructor~SmartPointer() {delete ptr;}// Overload dereference operator *T& operator*() {return *ptr;}// Overload member access operator ->T* operator->() {return ptr;}// Overload boolean conversion operatoroperator bool() const {return ptr != nullptr;}// Copy constructorSmartPointer(const SmartPointer& other) {ptr = new T(*(other.ptr));}// Copy assignment operatorSmartPointer& operator=(const SmartPointer& other) {if (this != &other) {delete ptr;ptr = new T(*(other.ptr));}return *this;}// Move constructorSmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {other.ptr = nullptr;}// Move assignment operatorSmartPointer& operator=(SmartPointer&& other) noexcept {if (this != &other) {delete ptr;ptr = other.ptr;other.ptr = nullptr;}return *this;}// Disable copy and move semantics for simplicitySmartPointer(const SmartPointer&) = delete;SmartPointer& operator=(const SmartPointer&) = delete;
};class Test {
public:void show() {std::cout << "Test::show() called" << std::endl;}
};int main() {SmartPointer<Test> sp1(new Test());SmartPointer<Test> sp2;if (sp1) {std::cout << "sp1 is not NULL" << std::endl;sp1->show();} else {std::cout << "sp1 is NULL" << std::endl;}if (sp2) {std::cout << "sp2 is not NULL" << std::endl;} else {std::cout << "sp2 is NULL" << std::endl;}return 0;
}
  1. 布尔类型转换运算符

    • operator bool() const { return ptr != nullptr; }: 重载布尔类型转换运算符,使智能指针在布尔上下文中可以直接使用。这将返回指针是否为非 NULL
  2. 使用示例

    • 创建了两个 SmartPointer 实例:sp1sp2sp1 指向一个 Test 对象,sp2 未初始化指针(默认为 nullptr)。
    • 使用 if 语句检查每个智能指针是否为 NULL

通过这种方式,可以方便地检查智能指针是否为 NULL,从而提高代码的可读性和安全性。

‌‌‌‌  要将智能指针转换为普通指针(dumb pointer),可以提供一个成员函数来返回内部存储的原始指针。这样,你可以在需要时获得智能指针管理的原始指针。下面是修改后的 SmartPointer 类,包含一个返回原始指针的方法:

#include <iostream>template <typename T>
class SmartPointer {
private:T* ptr;public:// Constructorexplicit SmartPointer(T* p = nullptr) : ptr(p) {}// Destructor~SmartPointer() {delete ptr;}// Overload dereference operator *T& operator*() {return *ptr;}// Overload member access operator ->T* operator->() {return ptr;}// Overload boolean conversion operatoroperator bool() const {return ptr != nullptr;}// Function to get the raw pointerT* get() const {return ptr;}// Copy constructorSmartPointer(const SmartPointer& other) {ptr = new T(*(other.ptr));}// Copy assignment operatorSmartPointer& operator=(const SmartPointer& other) {if (this != &other) {delete ptr;ptr = new T(*(other.ptr));}return *this;}// Move constructorSmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {other.ptr = nullptr;}// Move assignment operatorSmartPointer& operator=(SmartPointer&& other) noexcept {if (this != &other) {delete ptr;ptr = other.ptr;other.ptr = nullptr;}return *this;}// Disable copy and move semantics for simplicitySmartPointer(const SmartPointer&) = delete;SmartPointer& operator=(const SmartPointer&) = delete;
};class Test {
public:void show() {std::cout << "Test::show() called" << std::endl;}
};int main() {SmartPointer<Test> sp(new Test
  1. 获取原始指针

    • T* get() const { return ptr; }:添加一个名为 get 的成员函数,它返回智能指针内部存储的原始指针。
  2. 使用示例

    • main 函数中,通过 sp.get() 获取原始指针 raw_ptr,并使用它调用 Test 对象的 show 方法。

通过这种方式,你可以在需要时从智能指针中提取原始指针,同时仍然享受智能指针带来的内存管理优势。这种方法使你能够在使用普通指针的代码中集成智能指针。

‌‌‌‌  为了处理智能指针与继承相关的类型转换,我们可以实现类型安全的上行转换(基类指针指向派生类对象)和下行转换(派生类指针指向基类对象)。这可以通过实现模板函数来完成,类似于标准库中的 std::dynamic_pointer_caststd::static_pointer_cast

#include <iostream>
#include <memory>  // For std::dynamic_pointer_cast and std::static_pointer_casttemplate <typename T>
class SmartPointer {
private:T* ptr;public:// Constructorexplicit SmartPointer(T* p = nullptr) : ptr(p) {}// Destructor~SmartPointer() {delete ptr;}// Overload dereference operator *T& operator*() const {return *ptr;}// Overload member access operator ->T* operator->() const {return ptr;}// Overload boolean conversion operatoroperator bool() const {return ptr != nullptr;}// Function to get the raw pointerT* get() const {return ptr;}// Function to release ownership of the pointerT* release() {T* temp = ptr;ptr = nullptr;return temp;}// Function to reset the pointer with a new valuevoid reset(T* p = nullptr) {delete ptr;ptr = p;}// Disable copy constructor and copy assignment operator for simplicitySmartPointer(const SmartPointer&) = delete;SmartPointer& operator=(const SmartPointer&) = delete;// Move constructorSmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {other.ptr = nullptr;}// Move assignment operatorSmartPointer& operator=(SmartPointer&& other) noexcept {if (this != &other) {delete ptr;ptr = other.ptr;other.ptr = nullptr;}return *this;}
};// Dynamic cast for SmartPointer
template <typename T, typename U>
SmartPointer<T> dynamic_pointer_cast(const SmartPointer<U>& sp) {return SmartPointer<T>(dynamic_cast<T*>(sp.get()));
}// Static cast for SmartPointer
template <typename T, typename U>
SmartPointer<T> static_pointer_cast(const SmartPointer<U>& sp) {return SmartPointer<T>(static_cast<T*>(sp.get()));
}class Base {
public:virtual void show() {std::cout << "Base::show() called" << std::endl;}virtual ~Base() = default;
};class Derived : public Base {
public:void show() override {std::cout << "Derived::show() called" << std::endl;}
};int main() {SmartPointer<Derived> d(new Derived());d->show();SmartPointer<Base> b = static_pointer_cast<Base>(d);b->show();SmartPointer<Derived> d2 = dynamic_pointer_cast<Derived>(b);if (d2) {d2->show();} else {std::cout << "Dynamic cast failed" << std::endl;}return 0;
}
  1. 智能指针类
    • SmartPointer<T> 类实现了基本的智能指针功能,包括构造函数、析构函数、解引用操作符、成员访问操作符、布尔类型转换操作符和获取原始指针的方法。
    • 还实现了移动构造函数和移动赋值操作符。
  2. 类型转换
    • dynamic_pointer_caststatic_pointer_cast 模板函数实现了智能指针之间的动态转换和静态转换。
    • 这些函数使用 dynamic_caststatic_cast 将原始指针转换为目标类型,并返回一个新的智能指针。
  3. 使用示例
    • main 函数中,创建了一个 SmartPointer<Derived> 实例,并使用 static_pointer_cast 将其转换为 SmartPointer<Base>
    • 使用 dynamic_pointer_castSmartPointer<Base> 转换回 SmartPointer<Derived>,并检查转换是否成功。

这种实现方式使得智能指针可以安全地处理继承层次结构中的类型转换,类似于标准库中的 std::shared_ptrstd::unique_ptr 的行为。

‌‌‌‌  在智能指针类中,处理 const 修饰符是非常重要的,因为它有助于确保对象的不可变性和安全性。我们可以通过重载智能指针类的成员函数来支持 const 修饰符。下面是一个改进的 SmartPointer 类示例,它支持 const 智能指针:

#include <iostream>template <typename T>
class SmartPointer {
private:T* ptr;public:// Constructorexplicit SmartPointer(T* p = nullptr) : ptr(p) {}// Destructor~SmartPointer() {delete ptr;}// Overload dereference operator *T& operator*() {return *ptr;}const T& operator*() const {return *ptr;}// Overload member access operator ->T* operator->() {return ptr;}const T* operator->() const {return ptr;}// Overload boolean conversion operatoroperator bool() const {return ptr != nullptr;}// Function to get the raw pointerT* get() const {return ptr;}// Function to release ownership of the pointerT* release() {T* temp = ptr;ptr = nullptr;return temp;}// Function to reset the pointer with a new valuevoid reset(T* p = nullptr) {delete ptr;ptr = p;}// Disable copy constructor and copy assignment operator for simplicitySmartPointer(const SmartPointer&) = delete;SmartPointer& operator=(const SmartPointer&) = delete;// Move constructorSmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {other.ptr = nullptr;}// Move assignment operatorSmartPointer& operator=(SmartPointer&& other) noexcept {if (this != &other) {delete ptr;ptr = other.ptr;other.ptr = nullptr;}return *this;}
};// Dynamic cast for SmartPointer
template <typename T, typename U>
SmartPointer<T> dynamic_pointer_cast(const SmartPointer<U>& sp) {return SmartPointer<T>(dynamic_cast<T*>(sp.get()));
}// Static cast for SmartPointer
template <typename T, typename U>
SmartPointer<T> static_pointer_cast(const SmartPointer<U>& sp) {return SmartPointer<T>(static_cast<T*>(sp.get()));
}class Base {
public:virtual void show() {std::cout << "Base::show() called" << std::endl;}virtual ~Base() = default;
};class Derived : public Base {
public:void show() override {std::cout << "Derived::show() called" << std::endl;}
};void print(const SmartPointer<Base>& sp) {sp->show();
}int main() {SmartPointer<Derived> d(new Derived());d->show();SmartPointer<Base> b = static_pointer_cast<Base>(d);b->show();SmartPointer<Derived> d2 = dynamic_pointer_cast<Derived>(b);if (d2) {d2->show();} else {std::cout << "Dynamic cast failed" << std::endl;}const SmartPointer<Base> cb(new Base());print(cb);  // Use const SmartPointerreturn 0;
}
  1. 重载解引用操作符和成员访问操作符
    • T& operator*() { return *ptr; }const T& operator*() const { return *ptr; }:分别用于非 constconst 智能指针的解引用操作。
    • T* operator->() { return ptr; }const T* operator->() const { return ptr; }:分别用于非 constconst 智能指针的成员访问操作。
  2. 使用 const 智能指针
    • main 函数中,创建了一个 const SmartPointer<Base> 实例,并通过 print 函数使用它。
    • print 函数接受一个 const SmartPointer<Base>& 参数,并调用其 show 方法。

这种方式使得智能指针可以在 const 和非 const 情况下都能正确工作,增强了智能指针的灵活性和安全性。

条款29: Reference counting(引用计数)

1. 引用计数的实现

‌‌‌‌  引用计数是一种用于管理动态内存分配的技术,特别是在智能指针的上下文中,它用于追踪对象被引用的次数,并在没有任何引用时自动释放对象的内存。引用计数的实现可以帮助防止内存泄漏和悬挂指针问题。要理解引用计数的概念,可以从以下几个方面来解释:

  1. 基本原理
    • 每个被管理的对象都关联一个计数器,记录有多少个指针引用该对象。
    • 当一个新的智能指针指向对象时,计数器增加。
    • 当一个智能指针被销毁或指向另一个对象时,计数器减少。
    • 当计数器减少到零时,表示没有智能指针再引用该对象,释放该对象的内存。
  2. 应用场景
    • 引用计数广泛用于智能指针(如 std::shared_ptr)来自动管理对象生命周期。
    • 常用于需要共享所有权的对象。
  3. 优缺点
    • 优点:自动内存管理,防止内存泄漏和悬挂指针。
    • 缺点:增加了一些内存和性能开销,无法处理循环引用。

示例实现

‌‌‌‌  下面是一个基本的引用计数智能指针的实现示例:

#include <iostream>template <typename T>
class SmartPointer {
private:T* ptr;int* ref_count;public:// Constructorexplicit SmartPointer(T* p = nullptr) : ptr(p), ref_count(new int(1)) {std::cout << "SmartPointer constructed. Ref count = " << *ref_count << std::endl;}// Copy constructorSmartPointer(const SmartPointer<T>& sp) : ptr(sp.ptr), ref_count(sp.ref_count) {++(*ref_count);std::cout << "SmartPointer copied. Ref count = " << *ref_count << std::endl;}// Destructor~SmartPointer() {--(*ref_count);std::cout << "SmartPointer destructed. Ref count = " << *ref_count << std::endl;if (*ref_count == 0) {delete ptr;delete ref_count;std::cout << "Memory released." << std::endl;}}// Overload assignment operatorSmartPointer<T>& operator=(const SmartPointer<T>& sp) {if (this != &sp) {// Decrement the old object's ref count--(*ref_count);if (*ref_count == 0) {delete ptr;delete ref_count;std::cout << "Memory released." << std::endl;}// Copy the data and increment the new object's ref countptr = sp.ptr;ref_count = sp.ref_count;++(*ref_count);std::cout << "SmartPointer assigned. Ref count = " << *ref_count << std::endl;}return *this;}// Overload dereference operator *T& operator*() {return *ptr;}// Overload member access operator ->T* operator->() {return ptr;}// Get the current reference count (for debugging purposes)int getRefCount() const {return *ref_count;}
};class Test {
public:void show() {std::cout << "Test::show() called" << std::endl;}
};int main() {SmartPointer<Test> sp1(new Test());{SmartPointer<Test> sp2 = sp1;SmartPointer<Test> sp3;sp3 = sp1;std::cout << "sp1 ref count: " << sp1.getRefCount() << std::endl;std::cout << "sp2 ref count: " << sp2.getRefCount() << std::endl;std::cout << "sp3 ref count: " << sp3.getRefCount() << std::endl;sp2->show();(*sp3).show();}std::cout << "sp1 ref count after scope: " << sp1.getRefCount() << std::endl;return 0;
}

解释:

  1. 构造函数
    • 初始化指针和引用计数。
    • 新建一个引用计数并初始化为 1。
  2. 拷贝构造函数
    • 复制智能指针时,复制指针和引用计数,并将引用计数增加。
  3. 析构函数
    • 减少引用计数,当引用计数为 0 时,释放内存。
  4. 赋值操作符
    • 先减少当前对象的引用计数,如果引用计数为 0,释放内存。
    • 然后复制新对象的指针和引用计数,并增加引用计数。
  5. 解引用和成员访问操作符
    • 提供对内部指针的访问。

‌‌‌‌  这种实现方式确保了对象在不再被任何智能指针引用时自动释放内存,同时有效管理了对象的生命周期。

2. Copy-on-Write(写时才复制)

‌‌‌‌  Copy-on-Write(写时复制,简称 COW)是一种优化技术,主要用于提高程序的性能和内存使用效率。COW 的基本思想是,当多个对象共享同一个资源(如数据缓冲区)时,如果没有写操作发生,则多个对象共享同一个副本;只有在一个对象尝试修改资源时,才会进行真正的复制操作,以确保每个对象都有自己的副本。
下面是一个使用智能指针实现 COW 的示例:

实现 Copy-on-Write
  1. 实现引用计数类
    • 负责管理资源的引用计数。
  2. 实现 COW 智能指针
    • 当需要修改资源时,进行深拷贝。
#include <iostream>
#include <cstring>class RefCount {
public:int count;RefCount() : count(1) {}void addRef() { ++count; }int release() { return --count; }
};class CowString {
private:char* data;RefCount* refCount;void detach() {if (refCount->count > 1) {--refCount->count;refCount = new RefCount();data = strdup(data);  // Create a copy of the data}}public:// ConstructorCowString(const char* str) {data = strdup(str);refCount = new RefCount();}// Copy constructorCowString(const CowString& other) {data = other.data;refCount = other.refCount;refCount->addRef();}// Destructor~CowString() {if (refCount->release() == 0) {delete refCount;free(data);}}// Assignment operatorCowString& operator=(const CowString& other) {if (this != &other) {if (refCount->release() == 0) {delete refCount;free(data);}data = other.data;refCount = other.refCount;refCount->addRef();}return *this;}// Overload subscript operator for non-const objects (writing)char& operator[](size_t index) {detach();  // Ensure a unique copy before modificationreturn data[index];}// Overload subscript operator for const objects (reading)const char& operator[](size_t index) const {return data[index];}// Function to print the stringvoid print() const {std::cout << data << " (refCount: " << refCount->count << ")" << std::endl;}
};int main() {CowString str1("Hello, World!");CowString str2 = str1;std::cout << "Initial state:" << std::endl;str1.print();str2.print();// Modify str1, triggering COWstr1[7] = 'C';str1[8] = '+';str1[9] = '+';std::cout << "\nAfter modifying str1:" << std::endl;str1.print();str2.print();return 0;
}
解释:
  1. 引用计数类
    • RefCount 类用于管理资源的引用计数。
    • 提供 addRefrelease 方法来增加和减少引用计数。
  2. COW 字符串类
    • CowString 类管理字符数组,并在写操作时进行复制。
    • 构造函数分配并初始化字符串,初始化引用计数。
    • 拷贝构造函数和赋值操作符复制指针和引用计数,并增加引用计数。
    • 析构函数减少引用计数,如果计数为零,释放资源。
    • detach 方法在写操作前检查引用计数,如果大于 1,则进行深拷贝。
    • 重载 operator[] 进行读写操作,读操作不进行复制,写操作在修改前进行深拷贝。
  3. 使用示例
    • 创建两个 CowString 对象 str1str2,并赋值使其共享同一字符串。
    • 修改 str1 时触发写时复制,确保 str2 不受影响。

通过这种方式,COW 技术可以在共享资源时节省内存,并在必要时确保数据的独立性。这在需要频繁读写操作且希望优化性能和内存使用的场景中非常有用。

3. Pointers,References,以及Copy-on-Write

‌‌‌‌  理解指针(Pointers)、引用(References)以及写时复制(Copy-on-Write, COW)是掌握C++中高效内存管理和优化技术的关键。这三者各有其应用场景和特性,下面逐一解释并结合示例代码进行说明。

1. 指针(Pointers)

‌‌‌‌  指针是存储变量地址的变量。通过指针,可以直接访问和修改内存中的对象。指针的特性使其非常灵活,但也增加了复杂性和错误的可能性(如空指针、悬挂指针)。

#include <iostream>int main() {int a = 5;int* ptr = &a;std::cout << "Value of a: " << a << std::endl;     // 输出变量的值std::cout << "Value via pointer: " << *ptr << std::endl; // 使用指针访问值*ptr = 10;std::cout << "New value of a: " << a << std::endl; // 修改后的值return 0;
}
2. 引用(References)

‌‌‌‌  引用是已存在变量的别名,一旦引用被初始化,它不能被改变为指向其他对象。引用提供了一种更安全和更直观的方式来访问对象。

#include <iostream>int main() {int a = 5;int& ref = a;std::cout << "Value of a: " << a << std::endl;std::cout << "Value via reference: " << ref << std::endl;ref = 10;std::cout << "New value of a: " << a << std::endl;return 0;
}
3. 写时复制(Copy-on-Write, COW)

‌‌‌‌  写时复制是一种优化技术,用于延迟对象的复制,直到需要对其进行写操作时才进行实际复制。这在需要频繁读取而较少写入的场景中非常有用,能显著提高性能和内存利用效率。

#include <iostream>
#include <cstring>class CowString {
private:char* data;int* ref_count;void detach() {if (*ref_count > 1) {--(*ref_count);data = strdup(data);  // 深拷贝数据ref_count = new int(1);}}public:CowString(const char* str) {data = strdup(str);ref_count = new int(1);}CowString(const CowString& other) {data = other.data;ref_count = other.ref_count;++(*ref_count);}~CowString() {if (--(*ref_count) == 0) {delete ref_count;free(data);}}CowString& operator=(const CowString& other) {if (this != &other) {if (--(*ref_count) == 0) {delete ref_count;free(data);}data = other.data;ref_count = other.ref_count;++(*ref_count);}return *this;}char& operator[](size_t index) {detach();  // 写操作前进行深拷贝return data[index];}const char& operator[](size_t index) const {return data[index];}void print() const {std::cout << data << " (ref_count: " << *ref_count << ")" << std::endl;}
};int main() {CowString str1("Hello, World!");CowString str2 = str1;std::cout << "Initial state:" << std::endl;str1.print();str2.print();str1[7] = 'C';str1[8] = '+';str1[9] = '+';std::cout << "\nAfter modifying str1:" << std::endl;str1.print();str2.print();return 0;
}
解释:
  1. 指针
    • int* ptr = &a; 创建一个指向 a 的指针。
    • 使用 *ptr 访问或修改 a 的值。
  2. 引用
    • int& ref = a; 创建一个引用 ref,它是 a 的别名。
    • 通过引用可以访问或修改 a
  3. 写时复制
    • CowString 类实现了 COW 技术。
    • 当调用 operator[] 进行写操作时,调用 detach 方法检查引用计数,如果大于 1,则进行深拷贝。
    • 拷贝构造函数和赋值操作符负责管理引用计数。

总结:

  • 指针 提供了灵活性,但需要小心管理内存和避免指针错误。
  • 引用 是一种更安全的指针替代方案,但不能重新绑定。
  • 写时复制 优化了内存和性能,在需要共享和复制对象时非常有用。

4. 一个引用计数基类

‌‌‌‌  实现一个引用计数基类可以有效管理资源的生命周期,并能被其他类继承以实现自动内存管理。下面是一个简单的引用计数基类 RefCounted,以及一个继承自该基类的示例类 MyClass

#include <iostream>class RefCounted {
private:int refCount;protected:// ConstructorRefCounted() : refCount(0) {}// Destructorvirtual ~RefCounted() {}public:// Increment the reference countvoid addRef() {++refCount;}// Decrement the reference count and delete the object if count reaches zerovoid release() {if (--refCount == 0) {delete this;}}// Get the current reference count (for debugging purposes)int getRefCount() const {return refCount;}
};class MyClass : public RefCounted {
public:MyClass() {std::cout << "MyClass constructed" << std::endl;}~MyClass() {std::cout << "MyClass destructed" << std::endl;}void sayHello() {std::cout << "Hello from MyClass!" << std::endl;}
};class SmartPointer {
private:RefCounted* ptr;public:// Constructorexplicit SmartPointer(RefCounted* p = nullptr) : ptr(p) {if (ptr) {ptr->addRef();}}// Copy constructorSmartPointer(const SmartPointer& sp) : ptr(sp.ptr) {if (ptr) {ptr->addRef();}}// Destructor~SmartPointer() {if (ptr) {ptr->release();}}// Assignment operatorSmartPointer& operator=(const SmartPointer& sp) {if (this != &sp) {if (ptr) {ptr->release();}ptr = sp.ptr;if (ptr) {ptr->addRef();}}return *this;}// Overload dereference operator *RefCounted& operator*() {return *ptr;}// Overload member access operator ->RefCounted* operator->() {return ptr;}// Get the raw pointer (for debugging purposes)RefCounted* get() const {return ptr;}
};int main() {SmartPointer sp1(new MyClass());{SmartPointer sp2 = sp1;SmartPointer sp3;sp3 = sp1;std::cout << "sp1 ref count: " << sp1.get()->getRefCount() << std::endl;std::cout << "sp2 ref count: " << sp2.get()->getRefCount() << std::endl;std::cout << "sp3 ref count: " << sp3.get()->getRefCount() << std::endl;sp2->sayHello();}std::cout << "sp1 ref count after scope: " << sp1.get()->getRefCount() << std::endl;return 0;
}
解释:
  1. 引用计数基类 RefCounted
    • RefCounted 类包含一个私有的引用计数器 refCount
    • addRef 方法增加引用计数。
    • release 方法减少引用计数,当计数减少到 0 时,删除对象。
    • getRefCount 方法返回当前的引用计数,主要用于调试。
  2. 示例类 MyClass
    • MyClass 继承自 RefCounted,并在构造函数和析构函数中输出信息,以便观察对象的创建和销毁。
  3. 智能指针类 SmartPointer
    • SmartPointer 类管理 RefCounted 对象的指针,并负责引用计数的增加和减少。
    • 构造函数、拷贝构造函数、赋值操作符和析构函数都负责管理引用计数。
    • 提供解引用操作符 * 和成员访问操作符 -> 以便访问底层对象。
  4. 使用示例
    • 创建一个 SmartPointer 实例 sp1,指向一个新的 MyClass 对象。
    • 在一个新的作用域中创建 sp2sp3,使它们指向同一个对象,并输出引用计数。
    • 观察对象的生命周期管理。

这种方式可以自动管理对象的生命周期,防止内存泄漏,并且能够安全地共享对象。

5. 自动操作引用次数

‌‌‌‌  在 C++ 中实现自动操作引用次数通常通过智能指针来完成,智能指针可以自动地管理资源的生命周期,包括自动增加和减少引用计数。这样可以有效地避免内存泄漏和悬挂指针的问题。下面是一个简单的示例,展示如何使用智能指针来实现自动操作引用次数。

#include <iostream>
#include <memory> // 包含智能指针的头文件class MyClass {
public:void sayHello() {std::cout << "Hello from MyClass!" << std::endl;}
};int main() {// 使用 std::shared_ptr 实现自动操作引用次数std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>();std::shared_ptr<MyClass> sp2 = sp1; // 自动增加引用次数std::cout << "sp1 use count: " << sp1.use_count() << std::endl; // 输出引用次数std::cout << "sp2 use count: " << sp2.use_count() << std::endl;sp2.reset(); // 自动减少引用次数std::cout << "sp1 use count after reset: " << sp1.use_count() << std::endl;std::cout << "sp2 use count after reset: " << (sp2 ? sp2.use_count() : 0) << std::endl; // 输出可能的引用次数(应为0)return 0;
}
解释:
  1. 使用 std::shared_ptr
    • std::shared_ptr 是 C++ 标准库提供的智能指针之一,用于共享所有权的场景。
    • 使用 std::make_shared 创建 MyClass 类型的对象,并初始化 sp1 智能指针。
    • sp2 指向 sp1,此时引用计数增加为 2。
  2. 输出引用次数
    • 使用 use_count() 方法输出智能指针当前的引用计数。
  3. 释放引用
    • 调用 reset() 方法释放 sp2 指向的对象,引用计数减少为 1。
  4. 输出减少后的引用次数
    • 再次调用 use_count() 输出减少后的引用计数。

‌‌‌‌  通过使用 std::shared_ptr,可以自动管理对象的生命周期,无需手动增加或减少引用计数,从而简化了代码并提高了安全性。这种方法能够有效避免因为手动管理引用计数而导致的错误,例如忘记释放资源或者过早释放资源的问题。

6. 将Reference Counting加到既有的Classes身上

‌‌‌‌  将引用计数(Reference Counting)加到已有的类中,可以通过修改类的实现来实现引用计数的自动管理。这里给出一个示例,展示如何修改一个简单的类,使其支持引用计数。

‌‌‌‌  假设有一个 MyClass 类,我们希望为其添加引用计数功能。

#include <iostream>class MyClass {
private:int* data;int refCount; // 引用计数public:// ConstructorMyClass(int value) : data(new int(value)), refCount(1) {std::cout << "MyClass constructed, data: " << *data << ", refCount: " << refCount << std::endl;}// Copy ConstructorMyClass(const MyClass& other) : data(other.data), refCount(1) {std::cout << "MyClass copied, data: " << *data << ", refCount: " << refCount << std::endl;}// Destructor~MyClass() {delete data;std::cout << "MyClass destructed" << std::endl;}// Assignment OperatorMyClass& operator=(const MyClass& other) {if (this != &other) {data = other.data;++refCount;std::cout << "MyClass assigned, data: " << *data << ", refCount: " << refCount << std::endl;}return *this;}// Getter for data (just an example)int getValue() const {return *data;}// Getter for reference countint getRefCount() const {return refCount;}
};int main() {MyClass obj1(5); // 创建一个 MyClass 对象MyClass obj2 = obj1; // 使用拷贝构造函数,引用计数增加MyClass obj3;obj3 = obj1; // 使用赋值运算符,引用计数再次增加std::cout << "obj1 value: " << obj1.getValue() << ", refCount: " << obj1.getRefCount() << std::endl;std::cout << "obj2 value: " << obj2.getValue() << ", refCount: " << obj2.getRefCount() << std::endl;std::cout << "obj3 value: " << obj3.getValue() << ", refCount: " << obj3.getRefCount() << std::endl;return 0;
}
解释:
  1. MyClass 类修改
    • MyClass 中添加了一个 refCount 成员变量,用于跟踪对象的引用计数。
    • 构造函数初始化 refCount 为 1。
    • 拷贝构造函数和赋值运算符负责递增 refCount
    • 析构函数释放资源,并输出析构信息。
  2. 主函数中的使用
    • main 函数中创建 MyClass 对象 obj1,并输出其初始引用计数。
    • 使用拷贝构造函数 obj2 = obj1; 和赋值运算符 obj3 = obj1; 分别增加 obj1 的引用计数。
    • 输出每个对象的值和引用计数。

‌‌‌‌  通过这种方式,可以实现对已有类的引用计数管理。需要注意的是,这里的实现是基于浅复制数据成员,如果 MyClass 拥有复杂资源(如动态分配的内存、文件句柄等),则需要实现深拷贝来避免资源释放问题。

条款30: Proxy classes(替身类,代理类)

1. 实现二维数组

‌‌‌‌  实现一个代理类(Proxy Class)来管理二维数组可以有效地对数组进行访问控制和边界检查。代理类允许我们在访问数组元素时添加自定义的行为,比如检查索引是否有效,或者在访问时进行统计等操作。下面是一个简单的示例,展示如何使用代理类来实现二维数组的访问和控制。

#include <iostream>
#include <vector>// 代理类 Proxy Class
template<typename T>
class Array2DProxy {
private:std::vector<std::vector<T>>& array;size_t rows;size_t cols;public:Array2DProxy(std::vector<std::vector<T>>& arr, size_t r, size_t c): array(arr), rows(r), cols(c) {}// 重载括号运算符 [],返回第 row 行的 vector 引用std::vector<T>& operator[](size_t row) {if (row >= rows) {throw std::out_of_range("Array2DProxy: index out of range");}return array[row];}// 获取数组的行数size_t numRows() const {return rows;}// 获取数组的列数size_t numCols() const {return cols;}
};// 二维数组类 Array2D
template<typename T>
class Array2D {
private:std::vector<std::vector<T>> data;size_t rows;size_t cols;public:// 构造函数Array2D(size_t r, size_t c) : rows(r), cols(c) {data.resize(rows, std::vector<T>(cols));}// 获取代理对象Array2DProxy<T> operator[](size_t row) {return Array2DProxy<T>(data, rows, cols);}// 获取数组的行数size_t numRows() const {return rows;}// 获取数组的列数size_t numCols() const {return cols;}
};int main() {// 创建二维数组Array2D<int> arr(3, 4);// 设置和访问数组元素arr[0][0] = 1;arr[0][1] = 2;arr[1][2] = 3;arr[2][3] = 4;// 输出数组元素for (size_t i = 0; i < arr.numRows(); ++i) {for (size_t j = 0; j < arr.numCols(); ++j) {std::cout << arr[i][j] << " ";}std::cout << std::endl;}// 试图访问超出边界的元素,将会抛出异常try {std::cout << arr[3][0] << std::endl; // 超出范围} catch (const std::out_of_range& e) {std::cerr << "Exception: " << e.what() << std::endl;}return 0;
}
解释:
  1. 代理类 Array2DProxy
    • Array2DProxy 类用于代理二维数组的访问。
    • 构造函数接受一个二维向量引用 arr,以及数组的行数 r 和列数 c
    • 重载了括号运算符 [],返回第 row 行的 vector 引用,并检查索引是否超出范围。
  2. 二维数组类 Array2D
    • Array2D 类管理二维数组的数据。
    • 构造函数初始化数组的行数和列数,并调整向量大小以容纳数据。
    • 重载了括号运算符 [],返回 Array2DProxy 对象,用于访问二维数组的元素。
  3. 主函数中的使用
    • 创建一个 Array2D<int> 对象 arr,表示一个 3 行 4 列的二维数组。
    • 使用代理对象 arr[0][0]arr[0][1] 等设置和访问数组元素。
    • 输出数组中的元素。
    • 尝试访问超出边界的元素,会抛出 std::out_of_range 异常。

通过这种方式,可以使用代理类来增强对二维数组的访问控制,同时可以方便地实现边界检查和其他自定义操作。

2. 区分operator[]的读写动作

‌‌‌‌  在 C++ 中,通过重载 operator[] 可以实现对类中数据成员的访问,但通常情况下,operator[] 并不直接区分读操作和写操作。然而,可以通过返回类型来实现类似于 const 和非 const 的重载,从而实现对读写操作的区分。

‌‌‌‌  考虑一个简单的类 Array,包含一个动态分配的整数数组,并实现了 operator[] 的重载。

#include <iostream>
#include <vector>class Array {
private:std::vector<int> data;public:// 构造函数Array(size_t size) : data(size) {}// 获取数组元素int& operator[](size_t index) {return data[index];}// 获取数组元素(const 重载)const int& operator[](size_t index) const {return data[index];}// 返回数组大小size_t size() const {return data.size();}
};int main() {Array arr(5);// 写操作arr[0] = 10;std::cout << "Value at index 0: " << arr[0] << std::endl;// 读操作int value = arr[0];std::cout << "Value read from index 0: " << value << std::endl;// 使用 const 对象进行读操作const Array& constArr = arr;int constValue = constArr[0];std::cout << "Value read from const object: " << constValue << std::endl;return 0;
}
解释:
  1. Array
    • Array 类包含一个私有的 std::vector<int> 成员 data,用于存储整数数组。
    • 构造函数根据指定的大小初始化数组大小。
    • operator[] 方法被重载为 const 和非 const 版本:
      • 非 const 版本返回 int&,允许修改数组元素。
      • const 版本返回 const int&,仅允许读取数组元素,不允许修改。
  2. 主函数中的使用
    • arr[0] = 10; 是写操作,将数组第一个元素设置为 10。
    • int value = arr[0]; 是读操作,从数组中读取第一个元素的值。
    • 使用 const Array& constArr = arr;arr 转换为常量引用,使用 constArr[0] 读取数组的第一个元素。

‌‌‌‌  通过这种方式,我们可以通过重载 operator[] 的 const 和非 const 版本来区分读写操作,从而提供更加灵活和安全的数组访问方式。

条款31: 让函数根据一个以上的对象类型来决定如何虚化

1. 虚函数和运行时类型信息(RTTI)

‌‌‌‌  在 C++ 中,如果你想要根据不同的对象类型来决定如何虚化(即使用不同的虚函数实现),可以结合虚函数和运行时类型信息(RTTI)。RTTI 允许你在运行时确定对象的实际类型,从而根据需要调用适当的虚函数。

#include <iostream>
#include <typeinfo> // 包含用于运行时类型信息的头文件// 基类 Base
class Base {
public:// 虚析构函数virtual ~Base() {}// 虚函数 foovirtual void foo() const {std::cout << "Base::foo()" << std::endl;}
};// 派生类 Derived1
class Derived1 : public Base {
public:// 重写基类的虚函数 foovoid foo() const override {std::cout << "Derived1::foo()" << std::endl;}
};// 派生类 Derived2
class Derived2 : public Base {
public:// 重写基类的虚函数 foovoid foo() const override {std::cout << "Derived2::foo()" << std::endl;}
};int main() {Base* ptr1 = new Derived1();Base* ptr2 = new Derived2();// 调用虚函数 foo(),根据实际对象类型调用不同的实现ptr1->foo();ptr2->foo();// 使用 RTTI 获取对象的类型信息if (typeid(*ptr1) == typeid(Derived1)) {std::cout << "ptr1 points to an object of type Derived1" << std::endl;} else if (typeid(*ptr1) == typeid(Derived2)) {std::cout << "ptr1 points to an object of type Derived2" << std::endl;}if (typeid(*ptr2) == typeid(Derived1)) {std::cout << "ptr2 points to an object of type Derived1" << std::endl;} else if (typeid(*ptr2) == typeid(Derived2)) {std::cout << "ptr2 points to an object of type Derived2" << std::endl;}delete ptr1;delete ptr2;return 0;
}
解释:
  1. 类结构
    • Base 类包含一个虚析构函数和一个虚函数 foo(),用于展示多态行为。
    • Derived1Derived2 类分别从 Base 派生,并重写了 foo() 虚函数。
  2. 主函数中的使用
    • main() 函数中,通过基类指针 Base* 分别指向 Derived1Derived2 对象。
    • 调用 ptr1->foo()ptr2->foo(),由于虚函数的多态性,会根据实际对象的类型调用相应的虚函数实现。
    • 使用 typeid(*ptr1)typeid(*ptr2) 来获取对象的实际类型信息,从而确定对象是 Derived1 还是 Derived2
  3. 输出结果
    • 根据 ptr1ptr2 指向的对象类型,输出不同的信息,以验证虚函数的多态性和 RTTI 的使用。

通过这种方式,你可以根据对象的实际类型来决定调用适当的虚函数实现,从而达到根据多个对象类型来虚化的目的。

2. 使用“非成员函数”的碰撞处理函数

‌‌‌‌  在游戏开发或者物理模拟中,碰撞处理是一个常见的需求。通常,碰撞处理函数可以设计为非成员函数,这样可以更灵活地处理不同类型的碰撞,同时也避免了直接修改类的成员函数,保持了单一职责原则。

示例代码

‌‌‌‌  假设有两种不同的物体 BallBox,它们都可以发生碰撞,我们将设计一个非成员函数来处理它们的碰撞。

#include <iostream>// Forward declarations
class Box;
class Ball;// 球 Ball 类
class Ball {
private:float radius;float x, y;public:Ball(float r, float x_, float y_) : radius(r), x(x_), y(y_) {}// Getter for radiusfloat getRadius() const {return radius;}// Getter for positionstd::pair<float, float> getPosition() const {return {x, y};}friend void handleCollision(const Ball& ball, const Box& box);
};// 盒子 Box 类
class Box {
private:float width, height;float x, y;public:Box(float w, float h, float x_, float y_) : width(w), height(h), x(x_), y(y_) {}// Getter for dimensionsstd::pair<float, float> getDimensions() const {return {width, height};}// Getter for positionstd::pair<float, float> getPosition() const {return {x, y};}friend void handleCollision(const Ball& ball, const Box& box);
};// 非成员函数处理碰撞
void handleCollision(const Ball& ball, const Box& box) {// 获取球和盒子的位置信息auto ballPos = ball.getPosition();auto boxPos = box.getPosition();// 获取球的半径和盒子的尺寸float ballRadius = ball.getRadius();auto boxDimensions = box.getDimensions();// 碰撞检测逻辑(简单的圆形与矩形碰撞检测)float ballX = ballPos.first;float ballY = ballPos.second;float boxLeft = boxPos.first - boxDimensions.first / 2.0f;float boxRight = boxPos.first + boxDimensions.first / 2.0f;float boxTop = boxPos.second + boxDimensions.second / 2.0f;float boxBottom = boxPos.second - boxDimensions.second / 2.0f;if (ballX + ballRadius >= boxLeft && ballX - ballRadius <= boxRight &&ballY + ballRadius >= boxBottom && ballY - ballRadius <= boxTop) {std::cout << "Collision detected between Ball and Box!" << std::endl;} else {std::cout << "No collision detected between Ball and Box." << std::endl;}
}int main() {Ball ball(1.0f, 0.0f, 0.0f); // 半径为1的球,位置在 (0, 0)Box box(2.0f, 2.0f, 1.0f, 1.0f); // 尺寸为2x2的盒子,中心位置在 (1, 1)// 调用非成员函数处理碰撞handleCollision(ball, box);return 0;
}
解释:
  1. 类设计
    • Ball 类和 Box 类分别表示球和盒子,每个类中包含了自己的位置和尺寸信息,以及相应的 getter 方法。
    • handleCollision 是一个非成员函数,接受一个 Ball 对象和一个 Box 对象作为参数,用来检测和处理它们之间的碰撞。
  2. 碰撞处理函数 handleCollision
    • 函数内部获取球和盒子的位置信息及尺寸信息。
    • 简单地进行碰撞检测,这里使用了一个基本的圆形与矩形的碰撞检测逻辑。
    • 如果检测到碰撞,则输出碰撞信息;否则输出未检测到碰撞的信息。
  3. 主函数中的使用
    • main() 函数中创建了一个球对象 ball 和一个盒子对象 box
    • 调用 handleCollision(ball, box); 来检测和处理球和盒子之间的碰撞。

‌‌‌‌  通过这种方式,使用非成员函数来处理碰撞,可以有效地提高代码的灵活性和可维护性,同时避免直接修改类的成员函数,保持了类的单一职责原则。

3. 继承 + 自行仿真的虚函数表格

‌‌‌‌  在 C++ 中,继承虚函数表是实现多态性的关键机制。通过继承,子类可以重写(覆盖)基类中的虚函数,从而根据对象的实际类型调用适当的函数实现。虚函数表(vtable)是 C++ 实现动态多态的一种方式,用于存储和管理虚函数的地址。

示例代码

‌‌‌‌  下面展示一个简单的示例,演示如何通过继承和虚函数表来实现自行仿真的虚函数调用。

#include <iostream>// 基类 Base
class Base {
public:// 虚析构函数virtual ~Base() {}// 虚函数,用于自行仿真virtual void simulate() const {std::cout << "Base::simulate()" << std::endl;}
};// 派生类 Derived
class Derived : public Base {
public:// 重写基类的虚函数 simulatevoid simulate() const override {std::cout << "Derived::simulate()" << std::endl;}
};int main() {// 基类指针指向派生类对象Base* basePtr = new Derived();// 调用虚函数,自动调用派生类中的实现basePtr->simulate();delete basePtr;return 0;
}
解释:
  1. 类设计
    • Base 类包含一个虚析构函数和一个虚函数 simulate(),用于演示自行仿真的虚函数调用。
    • Derived 类从 Base 类派生,并重写(覆盖)了 simulate() 虚函数。
  2. 主函数中的使用
    • main() 函数中,使用 Base* basePtr = new Derived(); 创建了一个基类指针 basePtr,指向一个派生类 Derived 对象。
    • 调用 basePtr->simulate(); 时,由于 simulate() 是虚函数,会根据 basePtr 指向的对象实际类型(这里是 Derived 类型),自动调用派生类中的 Derived::simulate() 实现。
  3. 输出结果
    • 程序运行时,输出 Derived::simulate(),表明成功调用了派生类中重写的虚函数。

通过继承和虚函数表,C++ 实现了动态多态性,使得程序可以根据对象的实际类型来调用适当的虚函数实现,从而达到自行仿真的效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/39946.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

差分进化算法(Differential Evolution)及其python实现

### 背景&#xff1a; 差分进化算法&#xff08;Differential Evolution&#xff0c;DE&#xff09;是一种基于种群的优化算法&#xff0c;旨在解决连续优化问题。DE 最初由 Price 和 Storn 提出&#xff0c;是一种简单但有效的全局优化算法&#xff0c;适用于具有非线性、非光…

rillflow运行时,跳转子应用时,页面循环自动刷新

rill-flow-ui执行运行完毕&#xff0c;生成如下运行url&#xff0c;http://110.108.190.18:8080/选中执行&#xff0c;跳转子应用循环自动刷新&#xff0c;debugger发现主应用在和微应用的默认地址建立链接失败&#xff0c; 改为运行 http://localhost:8080/&#xff0c;即可和…

excel表格如何换行,这几个操作方法要收藏好

Excel表格作为一款强大的数据处理工具&#xff0c;在日常工作和生活中被广泛应用。当需要在单元格内显示较长的文本内容或使数据更加清晰易读时&#xff0c;我们需要掌握一些换行技巧。下面将介绍几种常用的Excel换行方法&#xff1a; 一、使用快捷键换行 1、首先&#xff0c;…

iSDF改进优化笔记

《iSDF: Real-Time Neural Signed Distance Fields for Robot Perception》论文提出了一种实时神经签名距离场&#xff08;SDF&#xff09;重建的方法&#xff0c;该方法在多个方面表现优异。然而&#xff0c;仍有一些潜在的改进空间&#xff1a; 1. 扩展实验范围 更多数据集和…

openresty lua用Redis的Stream解决消息订阅问题

使用 Redis Streams 解决消息订阅和消费的问题&#xff0c;可以避免在订阅模式下的连接管理问题。下面是如何使用 OpenResty 和 Redis Streams 实现类似的功能。 配置 nginx.conf 确保你的 nginx.conf 文件中配置了 Lua 模块和 Redis 集群的连接信息&#xff1a; http {lua_…

暑假学习DevEco Studio第一天

学习目标&#xff1a; 掌握构建第一个ArkTS应用 学习内容&#xff1a; 容器的应用 创建流程 点击file&#xff0c;new-> create project 点击empty ->next 进入配置界面 点击finsh&#xff0c;生成下面图片 这里需要注意记住index.ets &#xff0c;这是显示页面 –…

五款免费可视化利器分享,助力打造数字孪生新体验!

在当今数据驱动的时代&#xff0c;可视化工具已成为各行各业不可或缺的助手。它们不仅能帮助我们更好地理解和分析数据&#xff0c;还能以直观、生动的方式呈现复杂信息&#xff0c;提升沟通和决策效率。本文将为大家介绍五款免费的可视化工具&#xff0c;总有一款适合你。 一…

selenium 获取请求头cookie信息

在做接口测试如登陆接口过于复杂&#xff0c;可以先使用UI自动化把cookie保存在本地供接口测试使用 import time from selenium import webdriver from selenium.webdriver.chrome.options import Optionsdef get_seeion():# 创建Chrome浏览器的Options对象chrome_options Op…

如何做好企业品牌推广,看这篇文章就够了

在当今竞争激烈的市场环境中&#xff0c;品牌推广策略与方式成为企业成功的关键。因为相比起企业&#xff0c;消费者会更愿意为品牌买单。那么企业如何将品牌推广做好呢?今日投媒网与您分享。 1.明确品牌定位与目标受众 一切推广活动的起点在于清晰的品牌定位。首先&#xf…

苹果ios安卓apk应用APP文件怎么修改手机APP显示的名称

当我们安装了一款 APP后&#xff0c;该 APP的名称可能就是我们看到的名称&#xff0c;那么我们可以通过修改手机 APP显示的名称来修改该 APP文件的名称&#xff0c;那么具体怎么操作呢&#xff1f;下面就给大家来介绍一下。 首先我们进入手机上的应用商店&#xff0c;然后在搜…

SolrCloud Autoscaling 自动添加副本

SolrCloud Autoscaling 自动添加副本 前言 问题描述 起因是这样的&#xff0c;我在本地调试 Solr 源码&#xff08;版本 7.7.3&#xff09;&#xff0c;用 IDEA 以 solrcloud 方式启动了 2 个 Solr 服务&#xff0c;如下所示&#xff1a; 上图的启动参数 VM Options 如下&am…

RocketMQ实战:一键在docker中搭建rocketmq和doshboard环境

在本篇博客中&#xff0c;我们将详细介绍如何在 Docker 环境中一键部署 RocketMQ 和其 Dashboard。这个过程基于一个预配置的 Docker Compose 文件&#xff0c;使得部署变得简单高效。 项目介绍 该项目提供了一套 Docker Compose 配置&#xff0c;用于快速部署 RocketMQ 及其…

美国商超入驻细节全面曝光,电竞外设产品的国际化浪潮即将席卷全球

近年来&#xff0c;随着电子竞技(简称电竞)行业的蓬勃发展&#xff0c;电竞外设产品也逐渐成为消费者关注的热点。近期&#xff0c;一系列美国商超入驻细节的全面曝光&#xff0c;预示着电竞外设产品的出海风潮即将到来。 电竞行业迅速崛起&#xff0c;全球市场规模年均增长超1…

ResNet50V2

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、ResNetV1和ResNetV2的区别 ResNetV2 和 ResNetV1 都是深度残差网络&#xff08;ResNet&#xff09;的变体&#xff0c;它们的主要区别在于残差块的设计和…

Spring Security在企业级应用中的应用

Spring Security在企业级应用中的应用 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将探讨如何在企业级应用中应用Spring Security&#xff0c;这是保…

美业系统实操:手机App如何查看员工业绩?美业门店管理系统Java源码分享

在当今竞争激烈的美业市场中&#xff0c;有效的管理对于提高效率、增强客户体验和推动业务增长至关重要。美业管理系统通过其各种功能和优势&#xff0c;成为现代美业企业不可或缺的利器。 ▶下面以博弈美业进行实操-手机App端如何查看员工业绩&#xff1f; 1.店主登录手机端…

不是大厂云用不起,而是五洛云更有性价比

明月代维的一个客户的大厂云境外云服务器再有几天就到期了&#xff0c;续费提醒那是提前一周准时到来&#xff0c;但是看到客户发来的续费价格截图&#xff0c;我是真的没忍住。这不就是在杀熟吗&#xff1f;就这配置续费竟然如此昂贵&#xff1f;说实话这个客户的服务器代维是…

关于vue3的一些前端面试题

1.ref() 响应式对象顶级响应式对象&#xff0c;可以在模板中直接使用不用添加 .value,可以直接使用ref() 对像更新&#xff0c;Vue会自动检测更新&#xff0c;然后更新Dom深层次的对象也可以是响应式&#xff0c;也会被追踪shallowRef() 是ref的浅层次表现&#xff0c;深层次的…

Ollama+OpenWeb UI搭建最简单的大模型交互界面

Open WebUI是一个专为大型语言模型&#xff08;LLMs&#xff09;设计的Web用户界面。这个界面提供了一个直观、响应迅速且易于使用的平台&#xff0c;使用户能够与本地运行的语言模型进行交互&#xff0c;就像与云服务中的模型交互一样。可以非常方便的调试、调用本地模型。你能…

贴片电阻:01A、01B、01C、01D分别是什么意思?

贴片电阻的识别方法&#xff1a; 1、数字索位标称法 (一般矩形片状电阻采用这种标称法) 数字索位标称法就是在电阻体上用三位数字来标明其阻值。它的第一位和第二位为有效数字&#xff0c;第三位表示在有效数字后面所加“0”的个数&#xff0e;这一位不会出现字母。例如&…