重载运算符 ->* C++

Overloading operator ->* in C++

本文关键字:C++ gt 运算符 重载      更新时间:2023-10-16

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

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

为了完整起见,这里有一个完整的、可编译的、最小的示例,深受我链接到的这篇论文的启发,并与一个小的使用演示一起精简,以便让您开始:

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

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

载运算符->*的两个参数应该是 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的论文中找到。