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
类型以便打印。