Оператор перегрузки → * в С++

У меня есть моя собственная реализация интеллектуального указателя, и теперь я пытаюсь решить проблему вызова функции-члена по ее указателю. Я не предоставляю какую-либо функцию get() (как правило, я предоставляю оператор- > , но я не хочу использовать ее для этой цели).

Мой вопрос: как выглядит подпись и тип возврата operator->*?

Ответ 1

Для полноты, здесь полный, компилируемый, минимальный пример, сильно вдохновленный этой бумагой

Ответ 2

operator->*() принимает два аргумента:

  • Объект, над которым он работает.
  • Используемый указатель-член.

Если указатель-член - это просто доступ для элемента данных, результат получается прямо: вы можете просто вернуть ссылку на элемент. Если это функция, то ситуация немного сложнее: оператор доступа к члену должен вместо этого возвращать вызываемый объект. Вызываемый объект принимает соответствующее количество аргументов и возвращает тип указателя-члена.

Я понимаю, что исходный вопрос отмечен С++ 03, но выполнение "правильной" реализации С++ 03 - довольно длительное упражнение по набору текста: вы должны сделать то, что удобно делать с помощью вариативных шаблонов в коде ниже для каждого числа аргументов. Таким образом, этот код использует С++ 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: это должно быть довольно прямолинейно, поэтому я их опускаю. Существуют две версии:

  • Одна версия, использующая указатель на не-функциональный элемент, который просто возвращает ссылочный элемент для данного указателя.
  • Одна версия с указателем на член функции, который возвращает подходящий объект 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)...);
    }
};

Хорошо, это довольно прямолинейно. Один сложный бит - тот факт, что аргументы, вызываемые оператором вызова функции, могут быть разных типов, чем те, которые указатель на элемент. В приведенном выше кодере описывается ситуация, просто пересылая их.

Недопустимый бит - это функция factory для указанного выше типа 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);
}

Хорошо, все! Это довольно немного кода с использованием необычных шаблонов С++ 11 variadic. Ввод этого материала для подачи его на С++ 03 - это не то, что я хотел бы представить. С положительной стороны, я думаю, что эти операторы могут быть нечленными функциями. То есть они могут быть реализованы в подходящем пространстве имен, в котором используются только эти операторы, и пустой тег-тип, который наследует тип интеллектуального указателя. Поскольку тег-тег является базой, операторы будут найдены через ADL и работают для всех умных указателей. Они могли бы, например, использовать operator->(), чтобы получить указатель, необходимый для построения callable.

Используя С++ 11, на самом деле достаточно разумно реализовать поддержку operator->*(), независимую от любого конкретного типа интеллектуального указателя. В приведенном ниже коде представлена ​​реализация и простое использование. Он использует тот факт, что эта версия может быть найдена только на основе ADL (у вас никогда не должно быть директивы или декларации для нее), и что интеллектуальные указатели, вероятно, реализуют 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; }
};

Ответ 3

Два аргумента для перегруженного оператора ->* должны быть 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;
}

который выводит "вызывающий". В реальном мире вам нужно будет использовать шаблоны для поддержки различных типов указателей. Более подробную информацию вы можете найти в статье Скотта Мейера.