【问题标题】:Overloading operator ->* in C++在 C++ 中重载运算符 ->*
【发布时间】:2014-12-24 08:45:19
【问题描述】:

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

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

【问题讨论】:

  • 对于@OP:here's a paper,它准确地描述了你想要什么。
  • C++11 标准第 5.5 节中描述了“指向成员运算符->*.*”。它们确实存在于 C++ 中,即使它们不常用。
  • 指向成员的指针,通过 C++ 常见问题解答:parashift.com/c++-faq/dotstar-vs-arrowstar.html
  • 加上一个由 The Paramagnetic Croissant 提供的链接。这是用户想要的实际实现。
  • 呸,C++03。 C++14 版本很漂亮。

标签: c++ operator-overloading c++03


【解决方案1】:

为了完整起见,这里有一个完整的、可编译的、最小的示例,深受paper I've linked to 的启发,并与一个小的使用演示一起进行了精简,以便您开始使用:

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

【讨论】:

    【解决方案2】:

    operator-&gt;*() 有两个参数:

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

    如果成员指针只是对数据成员的访问,结果很简单:您可以只返回对该成员的引用。如果它是一个函数,那么事情就有点复杂了:成员访问运算符需要返回一个可调用对象。可调用对象接受适当数量的参数并返回成员指针的类型。

    我意识到最初的问题是用 c++03 标记的,但是做一个“正确的”C++03 实现是一个相当冗长的打字练习:你必须在下面的代码中通过可变参数模板方便地完成什么对于每个数量的参数。因此,这段代码使用 C++11 主要是为了更清楚地显示需要什么并避免进行打字练习。

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

    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 并不是我真正喜欢的东西。从好的方面来说,我认为这些运算符可以是非成员函数。也就是说,它们可以在一个合适的命名空间中实现,该命名空间只包含这些运算符 using 和一个智能指针类型将从其继承的空标记类型。以 tag-tag 为基础,运算符将通过 ADL 找到并适用于所有智能指针。例如,他们可以使用operator-&gt;() 来获取构造callable 所需的指针。

    使用 C++11 实现对 operator-&gt;*() 的支持实际上是相当直接的,独立于任何特定的智能指针类型。下面的代码显示了实现和简单的使用。它利用了这个版本只能基于 ADL 找到(你不应该有一个 using 指令或声明)并且智能指针可能实现 operator-&gt;() 的事实:代码使用这个函数来获取智能指针的指针。 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; }
    };
    

    【讨论】:

      【解决方案3】:

      重载运算符-&gt;* 的两个参数应该是 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's paper找到更多详细信息。

      【讨论】:

        猜你喜欢
        • 2012-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-19
        • 1970-01-01
        相关资源
        最近更新 更多