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

五. 技术

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

请记住

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

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

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

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

c 复制代码
#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 函数来处理 intdouble 类型的参数,避免了隐式类型转换带来的潜在问题。

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

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

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

c 复制代码
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. 单例模式

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

c 复制代码
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个实例),可以使用一个静态计数器来跟踪对象的数量:

c 复制代码
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 操作符)或者希望对象只能在栈上分配(自动变量)。这种需求出现在需要对对象的生命周期进行更精确的管理或优化性能的场景中。

要求对象只能产生于堆中

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

c 复制代码
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 为私有或删除,以禁止在堆上分配。

c 复制代码
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 newoperator 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 拥有,不能被复制,但可以转移所有权。

c 复制代码
#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_ptr
    std::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 被销毁时自动释放。

c 复制代码
#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;
}

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

3. std::weak_ptr

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

c 复制代码
#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_ptr
        std::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_ptrstd::shared_ptr 的构造、赋值和析构。

std::unique_ptr

构造
  1. 默认构造 :创建一个空的 unique_ptr
  2. 指针构造 :将一个裸指针(raw pointer)封装进 unique_ptr
  3. 移动构造 :从另一个 unique_ptr 移动构造,转移所有权。
c 复制代码
#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 重新指向一个新的对象。
c 复制代码
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 超出作用域或显式析构时,它管理的资源会被自动释放。

c 复制代码
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 移动构造。
c 复制代码
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 重新指向一个新的对象。
c 复制代码
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 超出作用域或显式析构时,它管理的资源会被自动释放。

c 复制代码
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
c 复制代码
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,它们不会影响引用计数。

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

c 复制代码
#include <iostream>

template <typename T>
class SmartPointer {
private:
    T* ptr;

public:
    // Constructor
    explicit 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 constructor
    SmartPointer(const SmartPointer& other) {
        ptr = new T(*(other.ptr));
    }

    // Copy assignment operator
    SmartPointer& operator=(const SmartPointer& other) {
        if (this != &other) {
            delete ptr;
            ptr = new T(*(other.ptr));
        }
        return *this;
    }

    // Move constructor
    SmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }

    // Move assignment operator
    SmartPointer& operator=(SmartPointer&& other) noexcept {
        if (this != &other) {
            delete ptr;
            ptr = other.ptr;
            other.ptr = nullptr;
        }
        return *this;
    }

    // Disable copy and move semantics for simplicity
    SmartPointer(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 * operator
    return 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 的功能:

c 复制代码
#include <iostream>

template <typename T>
class SmartPointer {
private:
    T* ptr;

public:
    // Constructor
    explicit 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 operator
    operator bool() const {
        return ptr != nullptr;
    }

    // Copy constructor
    SmartPointer(const SmartPointer& other) {
        ptr = new T(*(other.ptr));
    }

    // Copy assignment operator
    SmartPointer& operator=(const SmartPointer& other) {
        if (this != &other) {
            delete ptr;
            ptr = new T(*(other.ptr));
        }
        return *this;
    }

    // Move constructor
    SmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }

    // Move assignment operator
    SmartPointer& operator=(SmartPointer&& other) noexcept {
        if (this != &other) {
            delete ptr;
            ptr = other.ptr;
            other.ptr = nullptr;
        }
        return *this;
    }

    // Disable copy and move semantics for simplicity
    SmartPointer(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 类,包含一个返回原始指针的方法:

c 复制代码
#include <iostream>

template <typename T>
class SmartPointer {
private:
    T* ptr;

public:
    // Constructor
    explicit 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 operator
    operator bool() const {
        return ptr != nullptr;
    }

    // Function to get the raw pointer
    T* get() const {
        return ptr;
    }

    // Copy constructor
    SmartPointer(const SmartPointer& other) {
        ptr = new T(*(other.ptr));
    }

    // Copy assignment operator
    SmartPointer& operator=(const SmartPointer& other) {
        if (this != &other) {
            delete ptr;
            ptr = new T(*(other.ptr));
        }
        return *this;
    }

    // Move constructor
    SmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }

    // Move assignment operator
    SmartPointer& operator=(SmartPointer&& other) noexcept {
        if (this != &other) {
            delete ptr;
            ptr = other.ptr;
            other.ptr = nullptr;
        }
        return *this;
    }

    // Disable copy and move semantics for simplicity
    SmartPointer(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

c 复制代码
#include <iostream>
#include <memory>  // For std::dynamic_pointer_cast and std::static_pointer_cast

template <typename T>
class SmartPointer {
private:
    T* ptr;

public:
    // Constructor
    explicit 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 operator
    operator bool() const {
        return ptr != nullptr;
    }

    // Function to get the raw pointer
    T* get() const {
        return ptr;
    }

    // Function to release ownership of the pointer
    T* release() {
        T* temp = ptr;
        ptr = nullptr;
        return temp;
    }

    // Function to reset the pointer with a new value
    void reset(T* p = nullptr) {
        delete ptr;
        ptr = p;
    }

    // Disable copy constructor and copy assignment operator for simplicity
    SmartPointer(const SmartPointer&) = delete;
    SmartPointer& operator=(const SmartPointer&) = delete;

    // Move constructor
    SmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }

    // Move assignment operator
    SmartPointer& 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 智能指针:

c 复制代码
#include <iostream>

template <typename T>
class SmartPointer {
private:
    T* ptr;

public:
    // Constructor
    explicit 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 operator
    operator bool() const {
        return ptr != nullptr;
    }

    // Function to get the raw pointer
    T* get() const {
        return ptr;
    }

    // Function to release ownership of the pointer
    T* release() {
        T* temp = ptr;
        ptr = nullptr;
        return temp;
    }

    // Function to reset the pointer with a new value
    void reset(T* p = nullptr) {
        delete ptr;
        ptr = p;
    }

    // Disable copy constructor and copy assignment operator for simplicity
    SmartPointer(const SmartPointer&) = delete;
    SmartPointer& operator=(const SmartPointer&) = delete;

    // Move constructor
    SmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }

    // Move assignment operator
    SmartPointer& 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 SmartPointer

    return 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. 优缺点
    • 优点:自动内存管理,防止内存泄漏和悬挂指针。
    • 缺点:增加了一些内存和性能开销,无法处理循环引用。

示例实现

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

c 复制代码
#include <iostream>

template <typename T>
class SmartPointer {
private:
    T* ptr;
    int* ref_count;

public:
    // Constructor
    explicit SmartPointer(T* p = nullptr) : ptr(p), ref_count(new int(1)) {
        std::cout << "SmartPointer constructed. Ref count = " << *ref_count << std::endl;
    }

    // Copy constructor
    SmartPointer(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 operator
    SmartPointer<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 count
            ptr = 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 智能指针
    • 当需要修改资源时,进行深拷贝。
c 复制代码
#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:
    // Constructor
    CowString(const char* str) {
        data = strdup(str);
        refCount = new RefCount();
    }

    // Copy constructor
    CowString(const CowString& other) {
        data = other.data;
        refCount = other.refCount;
        refCount->addRef();
    }

    // Destructor
    ~CowString() {
        if (refCount->release() == 0) {
            delete refCount;
            free(data);
        }
    }

    // Assignment operator
    CowString& 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 modification
        return data[index];
    }

    // Overload subscript operator for const objects (reading)
    const char& operator[](size_t index) const {
        return data[index];
    }

    // Function to print the string
    void 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 COW
    str1[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)

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

c 复制代码
#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)

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

c 复制代码
#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)

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

c 复制代码
#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

c 复制代码
#include <iostream>

class RefCounted {
private:
    int refCount;

protected:
    // Constructor
    RefCounted() : refCount(0) {}

    // Destructor
    virtual ~RefCounted() {}

public:
    // Increment the reference count
    void addRef() {
        ++refCount;
    }

    // Decrement the reference count and delete the object if count reaches zero
    void 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:
    // Constructor
    explicit SmartPointer(RefCounted* p = nullptr) : ptr(p) {
        if (ptr) {
            ptr->addRef();
        }
    }

    // Copy constructor
    SmartPointer(const SmartPointer& sp) : ptr(sp.ptr) {
        if (ptr) {
            ptr->addRef();
        }
    }

    // Destructor
    ~SmartPointer() {
        if (ptr) {
            ptr->release();
        }
    }

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

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 类,我们希望为其添加引用计数功能。

c 复制代码
#include <iostream>

class MyClass {
private:
    int* data;
    int refCount; // 引用计数

public:
    // Constructor
    MyClass(int value) : data(new int(value)), refCount(1) {
        std::cout << "MyClass constructed, data: " << *data << ", refCount: " << refCount << std::endl;
    }

    // Copy Constructor
    MyClass(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 Operator
    MyClass& 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 count
    int 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)来管理二维数组可以有效地对数组进行访问控制和边界检查。代理类允许我们在访问数组元素时添加自定义的行为,比如检查索引是否有效,或者在访问时进行统计等操作。下面是一个简单的示例,展示如何使用代理类来实现二维数组的访问和控制。

c 复制代码
#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[] 的重载。

c 复制代码
#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 允许你在运行时确定对象的实际类型,从而根据需要调用适当的虚函数。

c 复制代码
#include <iostream>
#include <typeinfo> // 包含用于运行时类型信息的头文件

// 基类 Base
class Base {
public:
    // 虚析构函数
    virtual ~Base() {}

    // 虚函数 foo
    virtual void foo() const {
        std::cout << "Base::foo()" << std::endl;
    }
};

// 派生类 Derived1
class Derived1 : public Base {
public:
    // 重写基类的虚函数 foo
    void foo() const override {
        std::cout << "Derived1::foo()" << std::endl;
    }
};

// 派生类 Derived2
class Derived2 : public Base {
public:
    // 重写基类的虚函数 foo
    void 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,它们都可以发生碰撞,我们将设计一个非成员函数来处理它们的碰撞。

c 复制代码
#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 radius
    float getRadius() const {
        return radius;
    }

    // Getter for position
    std::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 dimensions
    std::pair<float, float> getDimensions() const {
        return {width, height};
    }

    // Getter for position
    std::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++ 实现动态多态的一种方式,用于存储和管理虚函数的地址。

示例代码

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

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:
    // 重写基类的虚函数 simulate
    void 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++ 实现了动态多态性,使得程序可以根据对象的实际类型来调用适当的虚函数实现,从而达到自行仿真的效果。

相关推荐
yuanbenshidiaos3 分钟前
C++-----图
开发语言·c++·算法
就一枚小白6 分钟前
UE--如何用 Python 调用 C++ 及蓝图函数
c++·python·ue5
莫奈的日出12 分钟前
PS等软件学习笔记
笔记·学习·ps/pr学习笔记
codists25 分钟前
《计算机组成及汇编语言原理》阅读笔记:p121-p122
笔记·编程人·计算机组成及汇编语言原理
✿ ༺ ོIT技术༻27 分钟前
同步&异步日志系统:设计模式
linux·c++·设计模式
小娄写码33 分钟前
线程池原理
java·开发语言·jvm
m0_6305206433 分钟前
Python初识
开发语言·python
汇能感知1 小时前
光谱相机与普通相机的区别
经验分享·笔记·科技·相机
网安-轩逸3 小时前
IPv4地址表示法详解
开发语言·php
獨枭7 小时前
CMake 构建项目并整理头文件和库文件
c++·github·cmake