问题 为什么编译器会调用 - >运算符两次


以下代码摘录自: https://github.com/facebook/folly/blob/master/folly/Synchronized.h

我最近看了一下Folly库,发现了一些有趣的东西。请考虑以下示例:

#include <iostream>

struct Lock {
    void lock() {
        std::cout << "Locking" << std::endl;
    }
    void unlock() {
        std::cout << "Unlocking" << std::endl;
    }
};

template <class T, class Mutex = Lock >
struct Synchronized {
    struct LockedPtr {
        explicit LockedPtr(Synchronized* parent) : p(parent) {
            p->m.lock();
        }

        ~LockedPtr() {
            p->m.unlock();
        }

        T* operator->() {
            std::cout << "second" << std::endl;
            return &p->t;
        }

    private:
        Synchronized* p;
    };

    LockedPtr operator->() {
        std::cout << "first" << std::endl;
        return LockedPtr(this);
    }

private:
    T t;
    mutable Mutex m;
};

struct Foo {
    void a() {
        std::cout << "a" << std::endl;
    }
};

int main(int argc, const char *argv[])
{
    Synchronized<Foo> foo;
    foo->a();

    return 0;
}

输出是:

first
Locking
second
a
Unlocking

我的问题是:为什么这段代码有效?这个模式有名字吗?

- >运算符被调用两次,但它只写了一次。


4037
2017-09-11 08:11


起源

operator->用于锁定\解锁的用法是天才! - Viktor Sehr


答案:


因为这就是标准所说的:

13.5.6类成员访问[over.ref]

1) operator-> 应该是一个非静态的成员函数   参数。它使用实现类成员访问 -> postfix-expression -> id-expression  一种表达 x->m 被解释   如 (x.operator->())->m 对于一个类对象 x 类型 T 如果    T::operator->() 存在,如果操作员被选为最佳   通过重载解析机制匹配函数(13.3)。

(强调我的)

在你的情况下, x 是 foo 和 m 是 a(),现在, Synchronized 重载 operator->foo->a() 相当于:

(foo.operator->())->a();

foo.operator->() 是你在课堂上的重载 Synchronized,返回一个 LockedPtr,然后那个 LockedPtr 自称 operator->


12
2017-09-11 08:17



为什么p-> m.unlock()行也不打印“秒”? - gil_bz
@gil_bz:p是此行的一个点,因此不会调用 - >运算符 - Allan
行p-> m.lock()不打印原始“秒”吗? - gil_bz
@gil_baz:不,它没有。原始“秒”打印时 LockedPtr::operator-> 由线调用 foo->a();之后 Synchronized::operator-> 已经回来了。上面的标准引用用简单的英语说,当你使用时 -> 在表达式中发生的是实现调用重载 operator-> 然后继续调用它的结果,直到最终返回一个指针。 p->ANYTHING 不打电话 LockedPtr::operator-> 要么 Synchronized::operator->因为 p 既不是 LockedPtr 也不是 Synchronized。它是 Synchronized*。 - Steve Jessop


问问自己:它还有什么可能表现的?

记住,重载点 operator-> 根本就是智能指针类可以使用与原始指针相同的语法。也就是说,如果你有:

struct S
{
    T m;
};

你有一个指针 p 至 S,然后你访问 S::m 通过 p->m 不管是否 p 是一个 S* 或者一些 pointer_class<S> 类型。

使用之间也有区别 -> 和调用 operator-> 直:

pointer_class<S> pointerObj;
S* p = pointerObj.operator->();

请注意,如果使用重载 -> 没有自动下降额外的水平,会是什么 p->m 可能意味着什么?如何使用过载?


0
2017-09-11 08:33