在C++中重载运算符->*

13

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

我的问题是: operator->*的签名和返回类型应该是什么样子的?


9
@OP:这篇论文 恰好描述了你所需的内容。 - The Paramagnetic Croissant
2
“指向成员运算符->*.*”在C++11标准的第5.5节中有所描述。它们确实存在于C++中,即使它们并不常用。 - Michael Burr
成员指针,参见C++ FAQ:http://www.parashift.com/c++-faq/dotstar-vs-arrowstar.html - Thomas Matthews
1
这是由The Paramagnetic Croissant提供的链接中所提供的实际实现,将其加一。 - Ankur
啊,C++03版本太老旧了。C++14版本比较好看。 - Yakk - Adam Nevraumont
3个回答

7

为了完整起见,这里提供一个完整的、可编译的、最小化的示例,灵感来自于我链接的这篇论文,并通过剥离和小规模使用演示来引导您开始使用:

#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;
}

4
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类型:它将保存对对象和成员的指针,并在调用时应用它们:
#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使用并不是我喜欢的事情。但好在,我认为这些运算符可以作为非成员函数。也就是说,它们可以在适当的命名空间中实现,该命名空间仅包含这些运算符和一个空标签类型,智能指针类型将从中继承。通过基本标记,运算符将通过ADL被发现,并适用于所有智能指针。例如,它们可以使用operator->()来获取构造callable所需的指针。

使用C++11,实际上很容易独立于任何特定的智能指针类型来实现operator->*()的支持。下面的代码显示了实现和简单使用。它利用了这个版本只能基于ADL找到(您不应该有对它的using指令或声明)且智能指针可能实现了operator->()的事实:代码使用这个函数来获取智能指针的指针。member_access名称空间可能应该放在一个适当的头文件中,只需由其他智能指针简单地继承member_access::member_access_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
->*运算符的重载需要两个参数: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;
}

该代码将输出"caller"。在实际应用中,您需要使用模板来支持各种成员指针类型。更多详细信息请参见Scott Meyer的论文


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