Ответ 1
Для полноты, здесь полный, компилируемый, минимальный пример, сильно вдохновленный этой бумагой
У меня есть моя собственная реализация интеллектуального указателя, и теперь я пытаюсь решить проблему вызова функции-члена по ее указателю. Я не предоставляю какую-либо функцию get() (как правило, я предоставляю оператор- > , но я не хочу использовать ее для этой цели).
Мой вопрос: как выглядит подпись и тип возврата operator->*
?
Для полноты, здесь полный, компилируемый, минимальный пример, сильно вдохновленный этой бумагой
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; }
};
Два аргумента для перегруженного оператора ->*
должны быть 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;
}
который выводит "вызывающий". В реальном мире вам нужно будет использовать шаблоны для поддержки различных типов указателей. Более подробную информацию вы можете найти в статье Скотта Мейера.