unique_ptr 如何同时支持 "dot" 和 "arrow" 调用以及未定义的方法?

How does unique_ptr support both "dot" and "arrow" invocation, and undefined methods?

据我所知,在 C++ 中,对任何对象的任何方法调用都必须在其 class 定义中预先定义。因此,当我查看 std::unique_ptr.

时,它变得很有趣

似乎unique_ptr同时支持"dot"(例如reset())和"arrow"操作。但是,"dot" 用于指针,而 "arrow" 用于 objects/references(我们可以做 ptr->MethodThatTheEncapsulatedClassSupports())。那么 unique_ptr 怎么可能既是指针又是对象呢?

第二个有趣的部分是,传递给 unique_ptr 的 class 可以是任意的。我可以在我的 class 中定义任何方法,而且我们似乎可以直接在 unique_ptr 实例上调用该方法。由于 C++ 没有像 Ruby 那样的动态方法调度机制(AFAIK C++ 方法调用是静态的并且必须定义,这是有道理的,因为 C++ 直接编译成机器代码),这怎么可能实现?

class 传递给 unique_ptr 的原因可以是任意的,因为 class 是一个模板参数。您应该阅读有关模板及其工作原理的信息,但本质上,在编译时自动为您使用的每个 class 提供一个 unique_ptr 版本。

-> 的工作方式是运算符重载。 unique_ptr 的实例不是指针,而是包含指针的对象,并且具有运算符重载,允许您使用 -> 访问该指针指向的对象中的数据。 .访问 unique_ptr 本身的方法。

这是因为 operator-> 可以重载到 return 另一个对象或指针。然后递归地与 operator-> 一起使用。

 class Dest
 {
     public:
        void test() {
            std::cout << "Test Called\n";
        }
 };
 class Forward
 {
     Dest d;
     public: 
         Dest* operator->() {
             return &d;
         }
 };
 int main()
 {
     Forward  f;
     f->test();    // operator-> on the object `f` returns a pointer to
                   // an object of type `Dest*`. Now apply re-apply the
                   // operator-> to the result.
                   //
                   // Since it is already a pointer this means access the
                   // member (which in this case is a method call).
 }

我能想到的最简单的智能指针实现是这样的:

#include <iostream>     
struct A { 
    void moo(){std::cout << "MOO" << std::endl;}
};

template <typename T>
struct DumbPointer {
    T* t;
    DumbPointer(T* t) : t(t) {}
    ~DumbPointer(){delete t;}
    T* operator->(){return t;}
    void moo(){std::cout << "MOOMOO" << std::endl;}
};     

int main() {
    DumbPointer<A> f{new A()};
    f.moo();              // prints MOOMOO
    f->moo();             // prints MOO     
    return 0;
}

重载 -> 运算符,使其看起来像一个指针,而实际上 DumbPointer 本身是一个对象。有关重载运算符的更多详细信息,请参见例如here。相关部分是:

If a user-defined operator-> is provided, the operator-> is called again on the value that it returns, recursively, until an operator-> is reached that returns a plain pointer. After that, built-in semantics are applied to that pointer.

PS:上面的例子是有保留的。例如DumbPointer<A> g = f;会导致不好的事情发生。

我发现 James O. Coplien 在他的书中的解释非常清楚易懂 Advanced C++。这是解释;

重载 operator-> 与其他重载 C++ 运算符的工作方式不同。对于所有其他运算符,定义运算符实现的主体对从运算中得到的值 return 具有最终控制权。对于 operator->,return 值是一个中间结果,然后应用 -> 的基本语义,产生一个结果。所以,

class B {
    ...
};

class A {
public:
    B *operator->();
};

int main() {
    A a;
    ... a->b ...
}

表示如下:

  1. 在对象 a 上调用 A::operator->()
  2. x 类型 B*;
  3. 的临时 x 中捕获来自该调用的 return 值
  4. 计算 x->b,得出结果。

这里,b 可以替换为 class B 的任何成员、数据或函数。如果 class B 重载 operator-> 则再次应用上述三个步骤。