C++重载解引用运算符。

47

我对C++还比较陌生,仍在努力掌握语法。我一直在研究一些运算符重载的例子,最近是智能指针实现。这是一个非常通用的例子:

template < typename T > class SP
{
    private:
    T*    pData; // Generic pointer to be stored
    public:
    SP(T* pValue) : pData(pValue)
    {
    }
    ~SP()
    {
        delete pData;
    }

    T& operator* ()
    {
        return *pData;
    }

    T* operator-> ()
    {
        return pData;
    }
};

当重载解引用运算符时,为什么类型是T&?同样地,当重载结构体解引用运算符时,为什么类型是T*?


13
"*p = 2" 是常用的语法,如果它没有任何作用就太遗憾了。 - chris
2
人们通常至少尝试在重载中保持一致性。C++11 5.3.1p1 [expr.unary.op]为指针类型规定了一个*运算符,模仿它并不罕见(包括对const的重载,如果您想要完整的范围)。 - WhozCraig
5个回答

69

解引用操作符 (*) 重载的工作方式与其他运算符重载类似。如果您想要能够修改解引用后的值,则需要返回一个非常量引用。这样,*sp = value 将实际修改 sp.pData 指向的值,而不是编译器生成的临时值。

结构体解引用操作符 (->) 重载是运算符重载的特殊情况。该运算符实际上在循环中调用,直到返回一个真实指针,然后对该真实指针进行解引用。我想这可能是他们能想到的唯一实现方式,结果有点 hackish。但它具有一些有趣的属性。假设您有以下类:

struct A {
    int foo, bar;
};

struct B {
    A a;
    A *operator->() { return &a; }
};

struct C {
    B b;
    B operator->() { return b; }
};

struct D {
    C c;
    C operator->() { return c; }
};

如果您有一个类型为D的对象d,调用d->bar将首先调用D::operator->(),然后调用C::operator->(),最后调用B::operator->(),这最终返回指向结构体A的真实指针,其bar成员以正常方式被解引用。请注意以下内容:
struct E1 {
    int foo, bar;
    E1 operator->() { return *this; }
};

调用类型为 E1 的变量 ee->bar 会产生无限循环。如果你想要实际解引用 e.bar,你需要这样做:

struct E2 {
    int foo, bar;
    E2 *operator->() { return this; }
};

总结一下:

  1. 重载解引用运算符时,类型应该为 T&,因为这是必要的,以修改由 pData 指向的值。
  2. 重载结构解引用时,类型应该为 T*,因为这个运算符是一个特殊情况,就是它的工作原理。

13
解引用运算符的目的是对指针进行解引用并返回对象引用。因此,它必须返回引用。这就是为什么它是T&。
引用运算符(->)的目的是返回指针,因此返回T*。

7
我认为这个问题源于“operator->的目的是返回指针”这一观点显得不太直观。造成困惑的原因是:“(*x).”和“x->”最终都会对x进行解引用并访问解引用指针的成员变量。因此,一个运算符被称为“返回指针”,另一个却返回引用,这一事实并不立即明显。 - johnb003
在 Rust 中,类似的概念非常容易理解。 - vikram2784

2
因为指针包含一个变量的地址,引用它将会给出一个引用(或者说是左值引用)到存储在其中的地址。例如:int x; int *p; p=&x; 现在 x*p 可以互换使用。如果你执行 x =4; 或者 *p = 4; 都会得到相同的结果。 *p 就像一个普通的引用 int& t = x; 一样,作为对 x 的引用。
接下来是结构体解引用运算符。这个运算符通过类对象的指针给你访问成员变量的权限。在上面的例子中,成员是 T* pData;,所以使用这个运算符将会给出对 pData 的访问权,并且 pDataT* 类型,所以返回类型是 T*。如果在上面的例子中 pDataT 类型,那么返回类型就会是 T

1
我将添加的问题“为什么C ++被设计成operator *和operator->在返回类型上不同?” 解释为“为什么operator->()重载也会在使用内置的->操作符时添加一个内置的解引用步骤 - 当重载可以直接返回T&而不添加任何后续步骤(除了在对象和必填成员之间添加一个隐含的.)。” - Ted Lyngmo

1

重载运算符就是函数

当你重载运算符时,基本上可以返回任何东西(就像任何其他函数一样),但要注意指针成员->有点特殊。

在你展示的例子中,智能指针的目的是模拟指针的语法(指针语义)。然后处理类似以下语法:

*p = 2;

间接引用运算符*返回一个非const引用,我们可以通过*修改对象。实际上,通常用于代理类型类似的方式,并且这是一种约定 - 但你理论上可以返回任何东西。
指针成员运算符->有点棘手。请参见this answer中的说明。

0
我们正在制作智能指针,我们的目标是使用任何 T&(引用)或 T*(指针)来灵活访问类/结构的成员。
对于 T* operator->():
T* operator-> ()
{
  return pData;
}
arrow operator (->) is a shorthand for (*p) and hence we return *pData which is of   type T*. 
here we are Overloding arrow operator so that members of T can be accessed like a pointer.

对于 T& operator* ():

T& operator* ()
{
  return *pData;
}
    * is "value at address" operator, here we are Overloding * (pointer) operator so 
    that members can be accessed  through a reference (T&).

我们可以通过运算符重载来改变运算符的行为,但这并不被推荐。重载运算符的行为应该尽可能接近其基本功能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接