五. 技术
条款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
函数来处理 int
和 double
类型的参数,避免了隐式类型转换带来的潜在问题。
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
。
理解和应用背景
限制类实例的数量的需求通常出现在以下情况:
- 资源有限:硬件资源或服务资源有限,只能允许特定数量的对象。
- 逻辑约束:业务逻辑要求只能存在特定数量的对象实例。
- 管理对象的生命周期:为了便于管理对象的创建和销毁,避免内存泄露或资源浪费。
总结来说,限制一个类所能创建的对象数量是一个设计决策,通常用于控制资源的分配、维持系统的稳定性和确保业务逻辑正确执行。通过使用静态成员变量和合适的逻辑控制,可以实现对类实例数量的限制。
条款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 new
和 operator new[]
删除,禁止对象在堆上分配。这样,尝试在堆上创建 StackOnly
对象会导致编译错误。
理解和应用背景
控制对象是否在栈上或堆上分配可以带来一些实际的好处:
- 生命周期管理:栈上分配的对象具有自动生命周期管理,出了作用域自动销毁,无需手动管理内存,可以避免内存泄露。
- 性能:栈分配速度通常比堆分配更快,因为栈是连续的内存空间,分配和释放开销较小。
- 设计约束:某些设计模式或API可能要求对象只能以特定方式分配,以确保功能或逻辑的正确性。
总结
通过控制类的构造函数和 operator new
,可以强制对象在栈上或堆上分配。这种技巧在需要精确管理对象生命周期和优化性能时非常有用。重要的是,设计这种控制时要结合具体需求和上下文,确保代码的可维护性和清晰性。
条款28: Smart Pointers(智能指针)
请记住:
- Smart Pointers的构造、赋值、析构
- 实现Dereferencing Operators(解引用操作符)
- 测试Smart Pointers是否为NULL
- 将Smart Pointers转换为Dumb Pointers
- Smart Pointers和"与继承有关的"类型转换
- Smart Pointers 与 const
解释 :
智能指针(Smart Pointers)是C++中的一种高级内存管理工具,它们通过封装裸指针来自动管理动态分配的内存,减少内存泄漏和其他与内存管理相关的问题。智能指针在标准库 <memory>
中定义,主要有以下几种类型:
std::unique_ptr
:独占所有权的智能指针。std::shared_ptr
:共享所有权的智能指针。std::weak_ptr
:非拥有型指针,配合std::shared_ptr
一起使用。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;
}
当 ptr1
和 ptr2
都超出作用域时,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_ptr
和 std::shared_ptr
的构造、赋值和析构。
std::unique_ptr
构造
- 默认构造 :创建一个空的
unique_ptr
。 - 指针构造 :将一个裸指针(raw pointer)封装进
unique_ptr
。 - 移动构造 :从另一个
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;
}
赋值
- 移动赋值:转移所有权。
- 通过
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
构造
- 默认构造 :创建一个空的
shared_ptr
。 - 指针构造 :将一个裸指针封装进
shared_ptr
。 - 工厂方法 :使用
std::make_shared
更加高效和安全。 - 复制构造:增加引用计数。
- 移动构造 :从另一个
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);
}
赋值
- 复制赋值:增加引用计数。
- 移动赋值:转移所有权。
- 通过
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
构造
- 默认构造 :创建一个空的
weak_ptr
。 - 从
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;
}
解释:
-
构造函数和析构函数:
SmartPointer(T* p = nullptr) : ptr(p) {}
: 构造函数接受一个指向类型T
的指针,并将其存储在智能指针的私有成员变量ptr
中。~SmartPointer() { delete ptr; }
: 析构函数在智能指针销毁时释放内存。
-
解引用操作符:
T& operator*() { return *ptr; }
: 重载解引用操作符*
,返回指向的对象的引用。T* operator->() { return ptr; }
: 重载成员访问操作符->
,返回指针以便访问对象的成员。
-
拷贝和移动语义:
- 拷贝构造函数和赋值操作符被删除以简化实现(但可以根据需求添加)。
- 移动构造函数和移动赋值操作符用于转移所有权。
使用示例:
在 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;
}
-
布尔类型转换运算符:
operator bool() const { return ptr != nullptr; }
: 重载布尔类型转换运算符,使智能指针在布尔上下文中可以直接使用。这将返回指针是否为非NULL
。
-
使用示例:
- 创建了两个
SmartPointer
实例:sp1
和sp2
。sp1
指向一个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
-
获取原始指针:
T* get() const { return ptr; }
:添加一个名为get
的成员函数,它返回智能指针内部存储的原始指针。
-
使用示例:
- 在
main
函数中,通过sp.get()
获取原始指针raw_ptr
,并使用它调用Test
对象的show
方法。
- 在
通过这种方式,你可以在需要时从智能指针中提取原始指针,同时仍然享受智能指针带来的内存管理优势。这种方法使你能够在使用普通指针的代码中集成智能指针。
为了处理智能指针与继承相关的类型转换,我们可以实现类型安全的上行转换(基类指针指向派生类对象)和下行转换(派生类指针指向基类对象)。这可以通过实现模板函数来完成,类似于标准库中的 std::dynamic_pointer_cast
和 std::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;
}
- 智能指针类 :
SmartPointer<T>
类实现了基本的智能指针功能,包括构造函数、析构函数、解引用操作符、成员访问操作符、布尔类型转换操作符和获取原始指针的方法。- 还实现了移动构造函数和移动赋值操作符。
- 类型转换 :
dynamic_pointer_cast
和static_pointer_cast
模板函数实现了智能指针之间的动态转换和静态转换。- 这些函数使用
dynamic_cast
和static_cast
将原始指针转换为目标类型,并返回一个新的智能指针。
- 使用示例 :
- 在
main
函数中,创建了一个SmartPointer<Derived>
实例,并使用static_pointer_cast
将其转换为SmartPointer<Base>
。 - 使用
dynamic_pointer_cast
将SmartPointer<Base>
转换回SmartPointer<Derived>
,并检查转换是否成功。
- 在
这种实现方式使得智能指针可以安全地处理继承层次结构中的类型转换,类似于标准库中的 std::shared_ptr
和 std::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;
}
- 重载解引用操作符和成员访问操作符 :
T& operator*() { return *ptr; }
和const T& operator*() const { return *ptr; }
:分别用于非const
和const
智能指针的解引用操作。T* operator->() { return ptr; }
和const T* operator->() const { return ptr; }
:分别用于非const
和const
智能指针的成员访问操作。
- 使用
const
智能指针 :- 在
main
函数中,创建了一个const SmartPointer<Base>
实例,并通过print
函数使用它。 print
函数接受一个const SmartPointer<Base>&
参数,并调用其show
方法。
- 在
这种方式使得智能指针可以在 const
和非 const
情况下都能正确工作,增强了智能指针的灵活性和安全性。
条款29: Reference counting(引用计数)
1. 引用计数的实现
引用计数是一种用于管理动态内存分配的技术,特别是在智能指针的上下文中,它用于追踪对象被引用的次数,并在没有任何引用时自动释放对象的内存。引用计数的实现可以帮助防止内存泄漏和悬挂指针问题。要理解引用计数的概念,可以从以下几个方面来解释:
- 基本原理 :
- 每个被管理的对象都关联一个计数器,记录有多少个指针引用该对象。
- 当一个新的智能指针指向对象时,计数器增加。
- 当一个智能指针被销毁或指向另一个对象时,计数器减少。
- 当计数器减少到零时,表示没有智能指针再引用该对象,释放该对象的内存。
- 应用场景 :
- 引用计数广泛用于智能指针(如
std::shared_ptr
)来自动管理对象生命周期。 - 常用于需要共享所有权的对象。
- 引用计数广泛用于智能指针(如
- 优缺点 :
- 优点:自动内存管理,防止内存泄漏和悬挂指针。
- 缺点:增加了一些内存和性能开销,无法处理循环引用。
示例实现
下面是一个基本的引用计数智能指针的实现示例:
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。
- 拷贝构造函数 :
- 复制智能指针时,复制指针和引用计数,并将引用计数增加。
- 析构函数 :
- 减少引用计数,当引用计数为 0 时,释放内存。
- 赋值操作符 :
- 先减少当前对象的引用计数,如果引用计数为 0,释放内存。
- 然后复制新对象的指针和引用计数,并增加引用计数。
- 解引用和成员访问操作符 :
- 提供对内部指针的访问。
这种实现方式确保了对象在不再被任何智能指针引用时自动释放内存,同时有效管理了对象的生命周期。
2. Copy-on-Write(写时才复制)
Copy-on-Write(写时复制,简称 COW
)是一种优化技术,主要用于提高程序的性能和内存使用效率。COW 的基本思想是,当多个对象共享同一个资源(如数据缓冲区)时,如果没有写操作发生,则多个对象共享同一个副本;只有在一个对象尝试修改资源时,才会进行真正的复制操作,以确保每个对象都有自己的副本。
下面是一个使用智能指针实现 COW 的示例:
实现 Copy-on-Write
- 实现引用计数类 :
- 负责管理资源的引用计数。
- 实现 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;
}
解释:
- 引用计数类 :
RefCount
类用于管理资源的引用计数。- 提供
addRef
和release
方法来增加和减少引用计数。
- COW 字符串类 :
CowString
类管理字符数组,并在写操作时进行复制。- 构造函数分配并初始化字符串,初始化引用计数。
- 拷贝构造函数和赋值操作符复制指针和引用计数,并增加引用计数。
- 析构函数减少引用计数,如果计数为零,释放资源。
detach
方法在写操作前检查引用计数,如果大于 1,则进行深拷贝。- 重载
operator[]
进行读写操作,读操作不进行复制,写操作在修改前进行深拷贝。
- 使用示例 :
- 创建两个
CowString
对象str1
和str2
,并赋值使其共享同一字符串。 - 修改
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;
}
解释:
- 指针 :
int* ptr = &a;
创建一个指向a
的指针。- 使用
*ptr
访问或修改a
的值。
- 引用 :
int& ref = a;
创建一个引用ref
,它是a
的别名。- 通过引用可以访问或修改
a
。
- 写时复制 :
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;
}
解释:
- 引用计数基类
RefCounted
:RefCounted
类包含一个私有的引用计数器refCount
。addRef
方法增加引用计数。release
方法减少引用计数,当计数减少到 0 时,删除对象。getRefCount
方法返回当前的引用计数,主要用于调试。
- 示例类
MyClass
:MyClass
继承自RefCounted
,并在构造函数和析构函数中输出信息,以便观察对象的创建和销毁。
- 智能指针类
SmartPointer
:SmartPointer
类管理RefCounted
对象的指针,并负责引用计数的增加和减少。- 构造函数、拷贝构造函数、赋值操作符和析构函数都负责管理引用计数。
- 提供解引用操作符
*
和成员访问操作符->
以便访问底层对象。
- 使用示例 :
- 创建一个
SmartPointer
实例sp1
,指向一个新的MyClass
对象。 - 在一个新的作用域中创建
sp2
和sp3
,使它们指向同一个对象,并输出引用计数。 - 观察对象的生命周期管理。
- 创建一个
这种方式可以自动管理对象的生命周期,防止内存泄漏,并且能够安全地共享对象。
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;
}
解释:
- 使用
std::shared_ptr
:std::shared_ptr
是 C++ 标准库提供的智能指针之一,用于共享所有权的场景。- 使用
std::make_shared
创建MyClass
类型的对象,并初始化sp1
智能指针。 sp2
指向sp1
,此时引用计数增加为 2。
- 输出引用次数 :
- 使用
use_count()
方法输出智能指针当前的引用计数。
- 使用
- 释放引用 :
- 调用
reset()
方法释放sp2
指向的对象,引用计数减少为 1。
- 调用
- 输出减少后的引用次数 :
- 再次调用
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;
}
解释:
MyClass
类修改 :- 在
MyClass
中添加了一个refCount
成员变量,用于跟踪对象的引用计数。 - 构造函数初始化
refCount
为 1。 - 拷贝构造函数和赋值运算符负责递增
refCount
。 - 析构函数释放资源,并输出析构信息。
- 在
- 主函数中的使用 :
- 在
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;
}
解释:
- 代理类
Array2DProxy
:Array2DProxy
类用于代理二维数组的访问。- 构造函数接受一个二维向量引用
arr
,以及数组的行数r
和列数c
。 - 重载了括号运算符
[]
,返回第row
行的vector
引用,并检查索引是否超出范围。
- 二维数组类
Array2D
:Array2D
类管理二维数组的数据。- 构造函数初始化数组的行数和列数,并调整向量大小以容纳数据。
- 重载了括号运算符
[]
,返回Array2DProxy
对象,用于访问二维数组的元素。
- 主函数中的使用 :
- 创建一个
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;
}
解释:
Array
类 :Array
类包含一个私有的std::vector<int>
成员data
,用于存储整数数组。- 构造函数根据指定的大小初始化数组大小。
operator[]
方法被重载为 const 和非 const 版本:- 非 const 版本返回
int&
,允许修改数组元素。 - const 版本返回
const int&
,仅允许读取数组元素,不允许修改。
- 非 const 版本返回
- 主函数中的使用 :
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;
}
解释:
- 类结构 :
Base
类包含一个虚析构函数和一个虚函数foo()
,用于展示多态行为。Derived1
和Derived2
类分别从Base
派生,并重写了foo()
虚函数。
- 主函数中的使用 :
- 在
main()
函数中,通过基类指针Base*
分别指向Derived1
和Derived2
对象。 - 调用
ptr1->foo()
和ptr2->foo()
,由于虚函数的多态性,会根据实际对象的类型调用相应的虚函数实现。 - 使用
typeid(*ptr1)
和typeid(*ptr2)
来获取对象的实际类型信息,从而确定对象是Derived1
还是Derived2
。
- 在
- 输出结果 :
- 根据
ptr1
和ptr2
指向的对象类型,输出不同的信息,以验证虚函数的多态性和 RTTI 的使用。
- 根据
通过这种方式,你可以根据对象的实际类型来决定调用适当的虚函数实现,从而达到根据多个对象类型来虚化的目的。
2. 使用"非成员函数"的碰撞处理函数
在游戏开发或者物理模拟中,碰撞处理是一个常见的需求。通常,碰撞处理函数可以设计为非成员函数,这样可以更灵活地处理不同类型的碰撞,同时也避免了直接修改类的成员函数,保持了单一职责原则。
示例代码
假设有两种不同的物体 Ball
和 Box
,它们都可以发生碰撞,我们将设计一个非成员函数来处理它们的碰撞。
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;
}
解释:
- 类设计 :
Ball
类和Box
类分别表示球和盒子,每个类中包含了自己的位置和尺寸信息,以及相应的 getter 方法。handleCollision
是一个非成员函数,接受一个Ball
对象和一个Box
对象作为参数,用来检测和处理它们之间的碰撞。
- 碰撞处理函数
handleCollision
:- 函数内部获取球和盒子的位置信息及尺寸信息。
- 简单地进行碰撞检测,这里使用了一个基本的圆形与矩形的碰撞检测逻辑。
- 如果检测到碰撞,则输出碰撞信息;否则输出未检测到碰撞的信息。
- 主函数中的使用 :
- 在
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;
}
解释:
- 类设计 :
Base
类包含一个虚析构函数和一个虚函数simulate()
,用于演示自行仿真的虚函数调用。Derived
类从Base
类派生,并重写(覆盖)了simulate()
虚函数。
- 主函数中的使用 :
- 在
main()
函数中,使用Base* basePtr = new Derived();
创建了一个基类指针basePtr
,指向一个派生类Derived
对象。 - 调用
basePtr->simulate();
时,由于simulate()
是虚函数,会根据basePtr
指向的对象实际类型(这里是Derived
类型),自动调用派生类中的Derived::simulate()
实现。
- 在
- 输出结果 :
- 程序运行时,输出
Derived::simulate()
,表明成功调用了派生类中重写的虚函数。
- 程序运行时,输出
通过继承和虚函数表,C++ 实现了动态多态性,使得程序可以根据对象的实际类型来调用适当的虚函数实现,从而达到自行仿真的效果。