跳到主要内容

C++ 成员访问运算符重载

在C++中,成员访问运算符是我们访问类和结构体成员的基本方式。C++允许我们重载这些运算符,为我们的自定义类型提供独特的成员访问行为。本文将介绍两种主要的成员访问运算符重载:箭头运算符(->)和成员指针运算符(.*->*)。

箭头运算符(->)重载

基本概念

箭头运算符(->)用于通过指针访问类或结构体的成员。当我们重载这个运算符时,可以创建"智能指针"类,它们能够模拟普通指针的行为,但同时提供额外的功能。

箭头运算符重载有一个特殊的规则:

重要规则

箭头运算符重载必须返回一个指针或者另一个定义了箭头运算符的对象。

语法

箭头运算符重载的语法如下:

cpp
class_type* operator->() {
// 实现代码
return pointer_to_actual_object;
}

实际示例:简单智能指针

下面是一个简单智能指针类的实现,它可以自动计算指针使用次数:

cpp
#include <iostream>
#include <string>

class Counter {
private:
static int count;
public:
Counter() { count++; }
~Counter() { count--; }
static int getCount() { return count; }
void display() const {
std::cout << "Counter object accessed!" << std::endl;
}
};

int Counter::count = 0;

// 简单的智能指针类
class SmartPointer {
private:
Counter* ptr;
public:
SmartPointer(Counter* p = nullptr) : ptr(p) {
std::cout << "构造智能指针" << std::endl;
}

~SmartPointer() {
std::cout << "析构智能指针" << std::endl;
delete ptr;
}

// 重载箭头运算符
Counter* operator->() {
std::cout << "箭头运算符被调用" << std::endl;
return ptr;
}
};

int main() {
{
SmartPointer sp = new Counter();

// 使用重载的箭头运算符调用display()方法
sp->display();

std::cout << "当前Counter对象数量: " << Counter::getCount() << std::endl;
} // sp在此作用域结束时被销毁

std::cout << "离开作用域后Counter对象数量: " << Counter::getCount() << std::endl;

return 0;
}

输出:

构造智能指针
箭头运算符被调用
Counter object accessed!
当前Counter对象数量: 1
析构智能指针
离开作用域后Counter对象数量: 0

箭头运算符重载的工作原理

当我们编写 sp->display() 时,编译器会将其转换为 (sp.operator->())->display()。也就是说:

  1. 首先调用 sp.operator->(),它返回一个 Counter* 指针
  2. 然后使用返回的指针调用 display() 方法

箭头运算符重载的实际应用场景

  1. 智能指针实现:C++标准库中的 std::shared_ptrstd::unique_ptr 等智能指针都重载了 -> 运算符。

  2. 代理模式:通过重载 -> 运算符,可以创建代理对象,在访问真实对象前执行额外的操作。

  3. 延迟加载:可以实现只有在实际需要时才加载资源的智能引用。

箭头运算符链式调用

箭头运算符可以链式调用,这是一个独特的特性。

cpp
#include <iostream>

class Inner {
public:
void process() { std::cout << "Processing..." << std::endl; }
};

class Middle {
private:
Inner* inner;
public:
Middle() : inner(new Inner()) {}
~Middle() { delete inner; }
Inner* operator->() { return inner; }
};

class Outer {
private:
Middle* middle;
public:
Outer() : middle(new Middle()) {}
~Outer() { delete middle; }
Middle* operator->() { return middle; }
};

int main() {
Outer outer;
// 链式调用:outer->操作将调用outer.operator->(),返回middle
// 然后middle->操作调用middle.operator->(),返回inner
// 最后通过inner调用process()方法
outer->process();
return 0;
}

输出:

Processing...

成员指针运算符(*和->*)重载

基本概念

C++还允许重载成员指针运算符 .*->*。这些运算符用于通过指向成员的指针访问成员。重载这些运算符的情况相对较少,但在某些高级设计模式中可能会用到。

语法

cpp
return_type operator->*(pointer_to_member right_operand) {
// 实现代码
}

return_type operator.*(pointer_to_member right_operand) {
// 实现代码
}

实际示例

cpp
#include <iostream>

class Base {
public:
void foo() { std::cout << "Base::foo()" << std::endl; }
void bar() { std::cout << "Base::bar()" << std::endl; }
};

class Wrapper {
private:
Base* ptr;
public:
Wrapper(Base* p) : ptr(p) {}

// 重载->*运算符
void operator->*(void (Base::*func)()) {
(ptr->*func)(); // 在Base对象上调用成员函数
}
};

int main() {
Base b;
Wrapper w(&b);

// 使用重载的->*运算符
w->*&Base::foo;
w->*&Base::bar;

return 0;
}

输出:

Base::foo()
Base::bar()

实际应用案例:自定义智能指针

下面是一个更完整的智能指针实现,它包含引用计数功能:

cpp
#include <iostream>
#include <string>

// 资源类
class Resource {
private:
std::string data;
public:
Resource(const std::string& d = "") : data(d) {
std::cout << "Resource 构造: " << data << std::endl;
}

~Resource() {
std::cout << "Resource 析构: " << data << std::endl;
}

void setData(const std::string& d) { data = d; }

std::string getData() const { return data; }
};

// 智能指针类
class SmartPtr {
private:
Resource* res;
int* refCount; // 引用计数

public:
// 构造函数
SmartPtr(Resource* r = nullptr) : res(r), refCount(nullptr) {
if (res) {
refCount = new int(1);
std::cout << "首次创建智能指针,引用计数: " << *refCount << std::endl;
}
}

// 复制构造函数
SmartPtr(const SmartPtr& sp) : res(sp.res), refCount(sp.refCount) {
if (refCount) {
(*refCount)++;
std::cout << "智能指针复制,引用计数增加为: " << *refCount << std::endl;
}
}

// 重载箭头运算符
Resource* operator->() {
if (res) {
std::cout << "使用箭头运算符访问资源" << std::endl;
return res;
}
throw std::runtime_error("访问空指针");
}

// 重载解引用运算符
Resource& operator*() {
if (res) {
return *res;
}
throw std::runtime_error("解引用空指针");
}

// 赋值运算符
SmartPtr& operator=(const SmartPtr& sp) {
if (this != &sp) {
// 减少原来引用的计数
if (refCount) {
(*refCount)--;
std::cout << "释放旧引用,引用计数减少为: " << *refCount << std::endl;

if (*refCount == 0) {
delete res;
delete refCount;
std::cout << "引用计数为0,释放资源" << std::endl;
}
}

// 指向新资源并增加引用计数
res = sp.res;
refCount = sp.refCount;

if (refCount) {
(*refCount)++;
std::cout << "智能指针赋值,引用计数增加为: " << *refCount << std::endl;
}
}
return *this;
}

// 析构函数
~SmartPtr() {
if (refCount) {
(*refCount)--;
std::cout << "智能指针析构,引用计数减少为: " << *refCount << std::endl;

if (*refCount == 0) {
delete res;
delete refCount;
std::cout << "引用计数为0,释放资源" << std::endl;
}
}
}
};

int main() {
{
std::cout << "=== 创建第一个智能指针 ===" << std::endl;
SmartPtr sp1(new Resource("数据1"));

{
std::cout << "\n=== 创建第二个指向相同资源的智能指针 ===" << std::endl;
SmartPtr sp2 = sp1;

std::cout << "\n=== 通过sp2访问资源 ===" << std::endl;
std::string data = sp2->getData();
std::cout << "获取的数据: " << data << std::endl;

std::cout << "\n=== 通过sp2修改资源 ===" << std::endl;
sp2->setData("更新的数据");

std::cout << "\n=== 通过sp1访问已修改的资源 ===" << std::endl;
std::cout << "获取的数据: " << sp1->getData() << std::endl;

std::cout << "\n=== 第二个智能指针离开作用域 ===" << std::endl;
}

std::cout << "\n=== 通过sp1继续访问资源 ===" << std::endl;
std::cout << "获取的数据: " << sp1->getData() << std::endl;

std::cout << "\n=== 第一个智能指针离开作用域 ===" << std::endl;
}

std::cout << "\n=== 程序结束 ===" << std::endl;
return 0;
}

输出:

=== 创建第一个智能指针 ===
Resource 构造: 数据1
首次创建智能指针,引用计数: 1

=== 创建第二个指向相同资源的智能指针 ===
智能指针复制,引用计数增加为: 2

=== 通过sp2访问资源 ===
使用箭头运算符访问资源
获取的数据: 数据1

=== 通过sp2修改资源 ===
使用箭头运算符访问资源

=== 通过sp1访问已修改的资源 ===
使用箭头运算符访问资源
获取的数据: 更新的数据

=== 第二个智能指针离开作用域 ===
智能指针析构,引用计数减少为: 1

=== 通过sp1继续访问资源 ===
使用箭头运算符访问资源
获取的数据: 更新的数据

=== 第一个智能指针离开作用域 ===
智能指针析构,引用计数减少为: 0
引用计数为0,释放资源
Resource 析构: 更新的数据

=== 程序结束 ===

成员访问运算符重载的注意事项

  1. 箭头运算符(->)只能作为成员函数重载,不能作为全局函数重载。

  2. 箭头运算符必须返回指针或定义了箭头运算符的对象,以便于链式调用。

  3. 成员指针运算符(.*->*)的重载较少见,但在某些高级设计模式中可能有用。

  4. 注意内存管理:如果你的智能指针类要管理资源,一定要正确处理复制、赋值和析构操作,以避免内存泄漏或访问已释放的内存。

总结

成员访问运算符重载是C++中的高级特性,主要用于创建智能指针和代理对象。箭头运算符(->)的重载尤其重要,它是实现智能指针类的基础。通过重载这些运算符,我们可以创建拥有普通指针语法但具备额外功能的类。

在实际开发中,现代C++已经提供了标准的智能指针类如std::shared_ptrstd::unique_ptr,它们都重载了箭头运算符。然而,了解如何自己实现这些功能对于深入理解C++和设计自定义类型仍然非常重要。

练习题

  1. 实现一个带有资源锁定/解锁功能的智能指针类,通过重载箭头运算符,确保每次访问资源前加锁,访问后解锁。

  2. 修改本文中的智能指针示例,添加一个计数器,记录通过箭头运算符访问资源的次数。

  3. 尝试实现一个可以延迟加载资源的智能指针,只有在第一次使用箭头运算符访问资源时才真正创建资源。

  4. 实现一个简单的调试智能指针,每次通过箭头运算符访问资源时,打印出资源的访问信息和调用的成员函数名称(提示:可能需要结合其他技术)。

更多资源

要深入了解成员访问运算符重载,推荐参考C++标准库中智能指针的实现,如std::shared_ptrstd::unique_ptr的源代码。此外,设计模式如代理模式、装饰器模式通常会利用这些运算符重载技术。