问题 在C ++中重载operator - > *


我有自己的智能指针实现,现在我试图解决通过其指针调用成员函数的问题。我没有提供任何类似get()的函数(实际上,我提供了一个operator->,但我不想为此目的使用它)。

我的问题是:签名和返回类型应该是什么 operator->* 看起来像?


1687
2017-12-24 08:45


起源

对于@OP: 这是一篇论文 描述 究竟 你想要什么。 - The Paramagnetic Croissant
“指向成员的运营商 ->* 和 .*“在5.5节中的C ++ 11标准中有描述。它们确实存在于C ++中,即使它们不常用。 - Michael Burr
指向成员的指针,通过C ++ FAQ: parashift.com/c++-faq/dotstar-vs-arrowstar.html - Thomas Matthews
加上一个由The Paramagnetic Croissant提供的链接。这是用户想要的实际实现。 - Ankur
Bah,C ++ 03。 C ++ 14版很漂亮。 - Yakk - Adam Nevraumont


答案:


为了完整起见,这是一个完整的,可编译的,最小的例子,深受此启发 我已经链接到了,以及一个小的使用演示,以便开始这个:

#include <memory>
#include <iostream>
#include <utility>


// Our example class on which we'll be calling our member function pointer (MFP)
struct Foo {
    int bar() {
        return 1337;
    }
};

// Return value of operator->* that represents a pending member function call
template<typename C, typename MFP>
struct PMFC {
    const std::unique_ptr<C> &ptr;
    MFP pmf;
    PMFC(const std::unique_ptr<C> &pPtr, MFP pPmf) : ptr(pPtr), pmf(pPmf) {}

    // the 'decltype' expression evaluates to the return type of ((C*)->*)pmf
    decltype((std::declval<C &>().*pmf)()) operator()() {
        return (ptr.get()->*pmf)();
    }
};

// The actual operator definition is now trivial
template<typename C, typename MFP>
PMFC<C, MFP> operator->*(const std::unique_ptr<C> &ptr, MFP pmf)
{
    return PMFC<C, MFP>(ptr, pmf);
}

// And here's how you use it
int main()
{
    std::unique_ptr<Foo> pObj(new Foo);
    auto (Foo::*pFn)() = &Foo::bar;
    std::cout << (pObj->*pFn)() << std::endl;
}

6
2017-12-24 10:25





operator->*() 有两个参数:

  1. 它正在运行的对象。
  2. 要应用的成员指针。

如果成员指针只是数据成员的访问权限,那么结果很简单:您只需返回对该成员的引用即可。如果它是一个函数,那么事情就会复杂一些:成员访问操作符需要返回一个可调用对象。可调用对象获取适当数量的参数并返回成员指针的类型。

我意识到原始问题用c ++ 03标记,但是做一个“正确的”C ++ 03实现是一个相当冗长的打字练习:你将不得不通过以下代码中的变量模板为每个数字方便地完成论据。因此,此代码主要使用C ++ 11来更清楚地显示所需内容并避免运行打字练习。

这是一个简单的“智能”指针定义 operator->*()

template <typename T>
class my_ptr
{
    T* ptr;
public:
    my_ptr(T* ptr): ptr(ptr) {}

    template <typename R>
    R& operator->*(R T::*mem) { return (this->ptr)->*mem; }

    template <typename R, typename... Args>
    struct callable;
    template <typename R, typename... Args>
    callable<R, Args...> operator->*(R (T::*mem)(Args...));
};

我认为,对于“适当”的支持,它也需要定义 const 版本:应该很直接,所以我省略了这些。基本上有两个版本:

  1. 一个版本获取指向非函数成员的指针,该成员只返回给定指针的引用成员。
  2. 一个版本获取指向返回合适的函数成员的指针 callable 目的。该 callable 将需要一个函数调用操作符并适当地应用它。

所以,接下来要定义的是 callable type:它将保存一个指向该对象的指针和一个指向该成员的指针,并在调用时应用它们:

#include <utility>
template <typename T>
     template <typename R, typename... Args>
struct my_ptr<T>::callable {
    T* ptr;
    R (T::*mem)(Args...);
    callable(T* ptr, R (T::*mem)(Args...)): ptr(ptr), mem(mem) {}

    template <typename... A>
    R operator()(A... args) const {
        return (this->ptr->*this->mem)(std::forward<A>(args)...);
    }
};

嗯,那是相当直接的。一个棘手的问题是函数调用操作符被调用的参数可能与指向成员的指针的类型不同。上面的代码只是转发它们来处理这种情况。

丢失的位是上述的工厂功能 callable 类型:

template <typename T>
    template <typename R, typename... Args>
my_ptr<T>::callable<R, Args...> my_ptr<T>::operator->*(R (T::*mem)(Args...)) {
    return callable<R, Args...>(this->ptr, mem);
}

好的,就是这样!这是使用花哨的C ++ 11可变参数模板的相当多的代码。输入这些东西以将其提供给C ++ 03并不是我想要的。从好的方面来说,我 认为 这些运算符可以是非成员函数。也就是说,它们可以在合适的命名空间中实现,该命名空间仅包含使用这些运算符和智能指针类型将继承的空标记类型。使用tag-tag作为基础,可以通过ADL找到运算符,并为所有智能指针工作。他们可以,例如,使用 operator->()抓住构造所需的指针 callable

使用C ++ 11实际上是合理地直接实现支持 operator->*() 独立于任何特定的智能指针类型。下面的代码显示了实现和简单的用法。它利用了这个版本只能基于ADL找到的事实(你永远不应该有一个using指令或声明),智能指针可能会实现 operator->():代码使用此函数来获取智能指针的指针。该 member_access 命名空间应该进入一个合适的标题,只是由其他智能指针包含,然后继承 member_access::member_acccess_tag (可以是一个 private(!)基类仍然会触发ADL查看 member_access)。

#include <utility>

namespace member_access
{
    struct member_access_tag {};

    template <typename Ptr, typename R, typename T>
    R& operator->*(Ptr ptr, R T::*mem) {
        return ptr.operator->()->*mem;
    }

    template <typename R, typename T, typename... Args>
    struct callable {
        T* ptr;
        R (T::*mem)(Args...);
        callable(T* ptr, R (T::*mem)(Args...)): ptr(ptr), mem(mem) {}

        template <typename... A>
        R operator()(A... args) const {
            return (this->ptr->*this->mem)(std::forward<A>(args)...);
        }
    };

    template <typename Ptr, typename R, typename T, typename... Args>
    callable<R, T, Args...> operator->*(Ptr ptr, R (T::*mem)(Args...)) {
        return callable<R, T, Args...>(ptr.operator->(), mem);
    }
}

template <typename T>
class my_ptr
    : private member_access::member_access_tag
{
    T* ptr;
public:
    my_ptr(T* ptr): ptr(ptr) {}
    T* operator->() { return this->ptr; }
};

2
2017-12-24 10:29





重载运算符的两个参数 ->* 应该是1.你的类的对象,和2.指向成员的指针。在最简单的情况下,它意味着重载的运算符应该是您的类的成员,接受一个类型指针指向成员的参数,因此例如对于没有参数的成员函数的指针,它将是:

TYPE operator->*(void (YourClass::*mp)());

返回类型应该是可调用的(在某种意义上说 operator() 适用于它)。最简单的方法是与另一个班级一起展示 - 这里有一个完整的例子:

struct Caller {
 void operator()() { cout << "caller"; }
};

struct A {
 void f() { cout << "function f"; }
 Caller operator->*(void (A::*mp)()) { return Caller(); }
};

int main() {
 A a;
 void (A::*mp)() = &A::f;
 (a->*mp)();
 return 0;
}

输出“来电”。在现实世界中,您需要使用模板来支持各种指针到成员类型。您可以在中找到更多详细信息 Scott Meyer的论文


1
2017-12-24 09:54