cpp
#include <iostream> // 用于输入输出,如 std::cout, std::endl
#include <string> // 图片中包含此头文件,尽管在此示例中未使用
// 定义 Entity 类,这是 ScopedPtr 将要管理的类型
class Entity {
public:
int x; // Entity 类的一个公共成员变量
// Entity 类的默认构造函数
Entity() : x(0) { // 初始化 x 为 0
std::cout << "Entity 构造" << std::endl;
}
// Entity 类的析构函数
~Entity() {
std::cout << "Entity 析构" << std::endl;
}
// Entity 类的一个公共成员函数
void Print() const { // const 表示此函数不会修改对象的状态
std::cout << "Hello! x = " << x << std::endl;
}
};
// 定义 ScopedPtr 类,一个简单的智能指针模拟
class ScopedPtr {
private:
Entity* m_Obj; // 内部封装一个裸指针,指向 Entity 对象
public:
// 构造函数:接受一个 Entity* 裸指针,接管其所有权
ScopedPtr(Entity* entity)
: m_Obj(entity) // 使用成员初始化列表初始化 m_Obj
{
// 构造函数体为空,因为初始化已在成员初始化列表中完成
std::cout << "ScopedPtr 构造,管理地址: " << m_Obj << std::endl;
}
// 析构函数:当 ScopedPtr 对象销毁时,自动释放其管理的 Entity 对象
~ScopedPtr() {
if (m_Obj) { // 确保 m_Obj 不是空指针,避免对空指针进行 delete
std::cout << "ScopedPtr 析构,释放地址: " << m_Obj << std::endl;
delete m_Obj; // 释放 m_Obj 指向的内存
m_Obj = nullptr; // 将指针置空,防止悬空指针
}
}
// 重载 operator->() (箭头运算符)
// 作用:允许通过 ScopedPtr 对象像访问指针一样访问 Entity 对象的成员
// 返回值:通常返回内部管理的裸指针
Entity* operator->() {
return m_Obj; // 返回内部存储的 Entity* 裸指针
}
// 重载 GetObject() 方法,返回内部裸指针
Entity* GetObject() { return m_Obj; }
// 禁用复制构造函数和复制赋值运算符,以实现独占所有权(类似 std::unique_ptr)
// 防止多个 ScopedPtr 共同管理同一块内存,导致重复释放。
ScopedPtr(const ScopedPtr&) = delete;
ScopedPtr& operator=(const ScopedPtr&) = delete;
};
int main() {
// 创建一个 ScopedPtr 对象 'entity',它将管理一个新创建的 Entity 对象。
// 使用直接初始化语法,这是创建智能指针的推荐方式。
ScopedPtr entity(new Entity());
// 通过重载的 operator->() 访问 Entity 对象的成员函数 Print()
// 编译时,entity->Print() 会被翻译为 (entity.operator->())->Print();
entity->Print();
// 访问 Entity 对象的成员变量 x (通过 operator->())
entity->x = 20; // 修改 x 的值
entity->Print(); // 再次打印,确认 x 已被修改
// 暂停程序,等待用户输入,以便观察输出和析构过程
std::cin.get();
// main 函数结束时,'entity' 对象会离开作用域,
// 其 ScopedPtr 类的析构函数会自动被调用,从而自动释放它所管理的 Entity 对象,避免内存泄漏。
return 0;
}
看着代码学习
重载->运算符
解析 entityPtr->Print(); 这行代码的执行过程:
- 当编译器看到
entityPtr->Print()时,它知道entityPtr是一个ScopedPtr类的对象,而不是一个裸指针。- 于是,编译器会寻找
ScopedPtr类中是否有operator->()的重载。- 它找到了你定义的
Entity* operator->()函数。- 编译器会调用
entityPtr.operator->()。entityPtr.operator->()执行,并返回entityPtr内部存储的那个Entity*类型的裸指针(即m_Obj的值)。- 然后,编译器会拿这个返回的
Entity*裸指针,再对其应用->Print()操作,最终调用到Entity类的Print()函数。
用箭头运算符获取内存中某值的偏移量笔记
目的: 这种技巧主要用于在编译时计算结构体(或类)成员相对于该结构体(或类)起始地址的偏移量。
核心思想: 利用 C/C++ 编译器在处理结构体成员访问时的特性,即编译器在编译阶段就已知结构体的内存布局和成员的相对位置。它不会真的去解引用一个空指针,而是计算成员的偏移量。
语法示例:
cppstruct Vector3 { float x, y, z; }; int main() { // 计算 Vector3 结构体中成员 z 的偏移量 int offset = (int)(&((Vector3*)nullptr)->z); // 1749396158901.png std::cout << offset << std::endl; // ... }解析步骤:
(Vector3*)nullptr: 将空指针nullptr强制转换为指向Vector3类型的指针。((Vector3*)nullptr)->z: 使用箭头运算符->访问Vector3结构体中的成员z。此时编译器会根据Vector3的定义,确定z相对于结构体起始地址的偏移量。&(...):&是取地址运算符,它获取的是z成员的地址。由于我们是基于nullptr(地址0)进行的操作,这个"地址"实际上就是z相对于结构体起始的偏移量。(int): 将计算得到的偏移量(通常是size_t类型)强制转换为int类型以便打印。