2014-12-24 19 views
11

Ho una mia implementazione di puntatore intelligente e ora sto cercando di risolvere il problema di chiamare la funzione membro tramite il suo puntatore. Non fornisco alcuna funzione get() - like (in realtà, fornisco un operatore->, ma non voglio usarlo per questo scopo).Operatore di overloading -> * in C++

La mia domanda è: quale dovrebbe essere la firma e il tipo di ritorno dello operator->*?

+9

Per @OP: [ecco un documento] (http://aristeia.com/Papers/DDJ_Oct_1999.pdf) che descrive ** esattamente ** ciò che si desidera. –

+2

"Gli operatori pointer-to-member' -> * 'e'. * '" Sono descritti nello standard C++ 11 nella sezione 5.5. Esistono in C++, anche se non sono comunemente usati. –

+0

Puntatore al membro, via C++ Domande frequenti: http://www.parashift.com/c++-faq/dotstar-vs-arrowstar.html –

risposta

6

Per completezza, ecco una completa, compilabile, esempio minimo, fortemente ispirato da questo paper I've linked to, e ridotta con un piccolo utilizzo demo al fine di iniziare con questo:

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

I due argomenti per l'operatore sovraccarico ->* devono essere 1. oggetto della classe e 2. puntatore al membro. Nel caso più semplice significa che l'operatore sovraccaricato dovrebbe essere un membro della classe accettare un argomento di tipo puntatore-a-membro, così ad esempio per un puntatore a funzione membro senza parametri sarebbe:

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

Il tipo restituito deve essere chiamabile (nel senso che lo si applica a operator()). È più facile da mostrare con un'altra classe: qui hai un esempio completo:

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

quale uscita "chiamante". Nel mondo reale è necessario utilizzare i modelli per supportare vari tipi di puntatore a membro. Maggiori dettagli si possono trovare in Scott Meyer's paper.

2

Il operator->*() prende due argomenti:

  1. L'oggetto su cui sta operando.
  2. Il puntatore membro da applicare.

Se il puntatore membro è solo un accesso per un membro dati, il risultato è semplice: è sufficiente restituire un riferimento al membro. Se è una funzione, beh, le cose sono un po 'più complicate: l'operatore di accesso membro deve invece restituire un oggetto callable. L'oggetto callable prende il numero appropriato di argomenti e restituisce il tipo del puntatore membro.

Mi rendo conto che la domanda originale è codificata con C++ 03 ma fare un'implementazione "corretta" di C++ 03 è un esercizio di digitazione piuttosto lungo: dovrete fare ciò che viene fatto comodamente dai modelli variadic nel codice sotto per ogni numero di argomenti. Quindi, questo codice usa C++ 11 principalmente per mostrare più chiaramente ciò che è necessario e per evitare di passare attraverso un esercizio di battitura.

Ecco un semplice puntatore "intelligente" che definisce 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...)); 
}; 

penso che per "vero" sostegno necessario anche definire const versioni: che dovrebbe essere abbastanza semplice in modo da sto omettendo questi. Esistono fondamentalmente due versioni:

  1. Una versione utilizza un puntatore a un membro non funzione che restituisce semplicemente il membro di riferimento per il puntatore specificato.
  2. Una versione prende un puntatore a un membro di funzione che restituisce un oggetto callable adatto.Lo callable dovrà avere un operatore di chiamata di funzione e applicarlo in modo appropriato.

Quindi, la prossima cosa da definire è il tipo callable: si terrà un puntatore all'oggetto e un puntatore al membro e applicarle su convocazione:

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

Beh, questo è abbastanza semplice inoltrare. L'unico trucchetto è il fatto che gli argomenti con cui viene chiamata l'operatore di chiamata di funzione possono essere di tipi diversi da quelli del puntatore al membro. Il codice sopra illustra la situazione semplicemente inoltrandoli.

Il bit mancante è la funzione di fabbrica per quanto sopra callable tipo:

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, questo è tutto! Questo è un bel po 'di codice usando i modelli variadici di fantasia C++ 11. Scrivere questa roba per darle da mangiare a un C++ 03 non è davvero qualcosa che mi piacerebbe. Sul lato positivo, I penso che questi operatori possono essere funzioni non membri. Cioè, potrebbero essere implementati in uno spazio dei nomi adatto che contiene solo questi operatori che utilizzano e un tipo di tag vuoto da cui un tipo di puntatore intelligente erediterà. Essendo il tag-tag una base, gli operatori potrebbero essere trovati tramite ADL e funzionare per tutti i puntatori intelligenti. Potrebbero, ad esempio, utilizzare operator->() per ottenere il puntatore necessario per costruire lo callable.

Utilizzando C++ 11 è in realtà ragionevolmente semplice implementare il supporto per operator->*() indipendente da qualsiasi tipo di puntatore intelligente specifico. Il codice seguente mostra l'implementazione e un utilizzo semplice. Fa uso del fatto che questa versione può essere trovata solo in base ad ADL (non si dovrebbe mai usare una direttiva o una dichiarazione per esso) e che i puntatori intelligenti probabilmente implementano operator->(): il codice usa questa funzione per ottenere il puntatore del puntatore intelligente . Lo spazio dei nomi member_access dovrebbe probabilmente entrare in un'intestazione adatta semplicemente inclusa da altri puntatori intelligenti che quindi ereditano semplicemente da member_access::member_acccess_tag (che può essere una classe base private (!) Come quella che attiva ancora ADL per cercare in 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; } 
};