Pytanie Przeciążanie operatora -> * w C ++


Mam własną implementację inteligentnego wskaźnika i teraz próbuję rozwiązać problem wywoływania funkcji składowej przez jej wskaźnik. Nie zapewniam żadnej funkcji get () - (tak naprawdę zapewniam operator->, ale nie chcę go używać w tym celu).

Moje pytanie brzmi: co powinien podpis i typ zwrotu operator->* wygląda jak?


11
2017-12-24 08:45


pochodzenie


Dla @OP: tutaj jest papier który opisuje dokładnie czego chcesz. - The Paramagnetic Croissant
"Wskaźniki do członków" ->* i .*"są opisane w standardzie C ++ 11 w sekcji 5.5. Istnieją one w C ++, nawet jeśli nie są powszechnie używane. - Michael Burr
Wskaźnik do członka, poprzez C ++ FAQ: parashift.com/c++-faq/dotstar-vs-arrowstar.html - Thomas Matthews
plus jeden dla linku dostarczonego przez Paramagnetic Croissant. Jest to rzeczywisty użytkownik implementacji, którego potrzebował. - Ankur
Bah, C ++ 03. Wersja C ++ 14 jest ładna. - Yakk - Adam Nevraumont


Odpowiedzi:


Dla kompletności, jest to kompletny, kompilacyjny, minimalny przykład, mocno zainspirowany tym papier, do którego mam linki rozebrany wraz z demo o niewielkim użytkowaniu, aby zacząć od tego:

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

6
2017-12-24 10:25





The operator->*() przyjmuje dwa argumenty:

  1. Obiekt, na którym działa.
  2. Wskaźnik członu do zastosowania.

Jeśli wskaźnik elementu jest po prostu dostępem do elementu danych, wynik jest prosty: możesz po prostu zwrócić referencję do elementu. Jeśli jest to funkcja, cóż, rzeczy są nieco bardziej skomplikowane: operator dostępu do elementu musi zamiast tego zwracać obiekt wywoływalny. Obiekt wywoływalny pobiera odpowiednią liczbę argumentów i zwraca typ wskaźnika członkowskiego.

Zdaję sobie sprawę, że oryginalne pytanie jest oznaczone tagiem c ++ 03, ale wykonanie "właściwej" implementacji C ++ 03 jest dość długim ćwiczeniem pisania: będziesz musiał zrobić to, co jest wygodnie wykonane przez szablony variadyczne w poniższym kodzie dla każdego numeru argumentów. Tak więc ten kod używa C ++ 11 przede wszystkim do wyraźniejszego pokazania, co jest potrzebne i uniknięcia wykonywania ćwiczeń na klawiaturze.

Oto proste "inteligentne" definiowanie wskaźnika 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...));
};

Myślę, że dla "właściwego" wsparcia trzeba to również zdefiniować const wersje: to powinno być całkiem proste, więc pomijam je. Zasadniczo istnieją dwie wersje:

  1. Jedna wersja przyjmuje wskaźnik do elementu niefunkcyjnego, który właśnie zwraca element odniesienia do danego wskaźnika.
  2. Jedna wersja przyjmuje wskaźnik do elementu funkcji, który zwraca odpowiedni callable obiekt. The callable będzie musiał mieć operatora wywołania funkcji i odpowiednio go zastosować.

Następną rzeczą do zdefiniowania jest callable type: przytrzyma wskaźnik na obiekcie i wskaźnik do elementu i zastosuje je podczas rozmowy:

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

Cóż, to dość proste. Jedynym trudnym bitem jest to, że argumenty, z którymi wywoływany jest operator wywołania funkcji, mogą być innego rodzaju niż te, które dotyczą wskaźnika do elementu. Powyższy kod dotyczy sytuacji, po prostu przekazując je.

Brakujący bit jest funkcją fabryczną dla powyższego callable rodzaj:

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

OK, to wszystko! Jest to całkiem sporo kodu przy użyciu fantazyjnych szablonów variadic C ++ 11. Wpisanie tego materiału w celu dostarczenia go do C ++ 03 nie jest czymś, co bym sobie wymyślił. Na plus, ja myśleć operatory te mogą być funkcjami nieczłonkowskimi. Oznacza to, że mogą one zostać zaimplementowane w odpowiedniej przestrzeni nazw, która zawiera tylko te operatory i pusty typ znacznika, z którego typ inteligentnego wskaźnika dziedziczy. Ponieważ tag-tag jest bazą, operatory zostaną znalezione za pośrednictwem ADL i będą działać dla wszystkich inteligentnych wskaźników. Mogliby np. Użyć operator->()aby uzyskać wskaźnik potrzebny do skonstruowania callable.

Korzystanie z C ++ 11 jest rzeczywiście dość proste do wdrożenia obsługi operator->*() niezależnie od konkretnego typu inteligentnego wskaźnika. Poniższy kod pokazuje implementację i proste użycie. Korzysta z faktu, że ta wersja może być znaleziona tylko w oparciu o ADL (nigdy nie powinieneś mieć dyrektywy lub deklaracji użycia) i te inteligentne wskaźniki prawdopodobnie implementują operator->(): kod wykorzystuje tę funkcję do uzyskania wskaźnika wskaźnika inteligentnego. The member_access przestrzeń nazw powinna prawdopodobnie przejść do odpowiedniego nagłówka po prostu dołączona przez inne inteligentne wskaźniki, które następnie dziedziczą po prostu member_access::member_acccess_tag (który może być private(!) Klasa podstawowa, która nadal powoduje, że ADL zajmie się 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
2017-12-24 10:29





Dwa argumenty dla przeciążonego operatora ->* powinien być 1. obiektem twojej klasy i 2. wskaźnikiem do członka. W najprostszym przypadku oznacza to, że przeciążony operator powinien być członkiem twojej klasy przyjmując jeden argument typu wskaźnik-do-członka, więc na przykład dla wskaźnika do funkcji składowej bez parametrów będzie to:

TYPE operator->*(void (YourClass::*mp)());

Typ zwrotu powinien być możliwy do wywołania (w tym sensie, że operator() dotyczy to). Najłatwiej pokazać innym klasom - tutaj masz pełny przykład:

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

który wysyła "dzwoniącego". W prawdziwym świecie musisz używać szablonów do obsługi różnych typów wskaźników. Więcej szczegółów można znaleźć w Artykuł Scotta Meyera.


1
2017-12-24 09:54