2013-05-31 14 views
19

Herb Sutter ha chiesto questa domanda un parlare di C++ 11 e la concorrenza (vedi this video)Come racchiudere le chiamate di ogni funzione membro di una classe in C++ 11?

L'idea chiave qui è di avere una classe non bloccaggio X dove ogni chiamata di funzione dovrebbe essere decorato con un blocco che è sbloccato dopo una funzione.

Tuttavia, Herb Sutter si allontana e presenta un approccio basato sul functor. Mi chiedo se è addirittura possibile con C++ 11 per avvolgere ogni chiamata di funzione con blocco e sblocco di una classe in un modo generico (non avvolgendo manualmente ogni chiamata di funzione).

class X { 
    public: 
    X() = default; 
    void somefunc(arg1 x1, arg2 x2, ...); 
    void somefunc2(arg1 x1, arg2 x2, ...); 
    /* and more */ 
}; 

// herb admits one way to make all functions *available* 
// in another class is by derivation 

class XX : public X { 
    public: 
    XX() = default; 
    // all functions available in NON overloaded form... 
}; 

c'è anche il modello decorator

class XXX { 
    public: 
    XXX(X &x) : m_x(x) {} 

    // explicitly call each wrapped function ... done for each class separately. 
    void somefunc(arg1 x1, arg2 x2, ...); 
    void somefunc2(arg1 x1, arg2 x2, ...); 
    private: 
    class X& m_x; 
}; 

, ma c'è qualcosa di simile a questo possibile:

template<> 
class wrap_everything; 

wrap_everything<X> x; 
x.somefunc(x1,x2,...); // this is then locked. 

per ragioni di completezza questo è l'approccio basato functor di Sutter erba:

template <class T> class locker { 
    private: 
    mutable T m_t; 
    mutable std::mutex m_m; 
    public: 
    locker(T t = T{}) : m_t(t) {} 
    template <typename F> 
    auto operator()(F f) const -> decltype(f(m_t)) { 
     std::lock_guard<mutex> _{m_m}; 
     return f(t); 
    } 
}; 


// usage 
locker<std::string> s; 
s([](string &s) { 
    s += "foobar"; 
    s += "barfoo"; 
}); 
+0

Almeno con alcuni compilatori (ad es. Gcc) il compilatore può eseguire questa operazione senza alcuna modifica del codice. Solitamente utilizzato per la profilazione, ma è possibile averlo inserire una chiamata a funzioni specificate prima e dopo ogni chiamata di funzione. Sarebbe comunque non banale che il codice risolva il punto in cui si desidera bloccare e dove invece non è vero. –

+0

@JerryCoffin Immagino che sia fatale per ogni manutentore dover cercare i lucchetti da qualche altra parte rispetto al codice. – Alex

+0

@Alex Ottima domanda. Ricordo quando Sutter si allettò con questo al C++ e oltre il 2012, e poi si allontanò, come dici tu. Forse stava lasciando dei suggerimenti sul set di funzionalità del C++ 14. –

risposta

15

La domanda riguarda il pattern EXECUTE-AROUND.Ho fatto un'implementazione generica (ma solo a malapena testato) di execute-AROUND puntatore https://gitlab.com/redistd/redistd/blob/master/include/redi/exec_around.h

Questo permette:

struct X { void f() { } }; 
auto x = mutex_around<X>(); 
x->f(); // locks a mutex for duration of call to X::f 

Una più approfondita spiegazione su come la famiglia di eseguire intorno modelli lavoro può essere trovato here (pdf)

+0

Non sono ancora sicuro se è quello che sto cercando, ma mi rende già eccitato! – Alex

+0

+1 Hah, giusto! Il comportamento di concatenamento di 'operator->'! Soluzione molto bella –

6

Non è possibile fare esattamente quello che vuoi, ma qualcosa di vicino è fattibile.

#include <iostream> 

class Foo { 
    public: 
    void one (int x) { 
     std::cout << "Called Foo::one(" << x << ")\n"; 
    } 
    void two (int x, double y) { 
     std::cout << "Called Foo::two(" << x << ", " << y << ")\n"; 
    } 
}; 

class ScopeDecorator { 
    public: 
    ScopeDecorator() { 
     std::cout << "Enter scope\n"; 
    } 
    ~ScopeDecorator() { 
     std::cout << "Exit scope\n"; 
    } 
}; 

template <class Wrappee, class Wrapper> 
class Wrap { 
    public: 
    Wrap (Wrappee& w) : wrappee(w) {} 
    template <typename rettype, typename... argtype> 
     rettype call (rettype (Wrappee::*func)(argtype...), argtype... args) 
     { 
      Wrapper wrapper; 
      return (wrappee.*func)(args...); 
     } 
    private: 
    Wrappee& wrappee; 
}; 

int main() 
{ 
    Foo foo; 
    Wrap<Foo, ScopeDecorator> wfoo(foo); 
    wfoo.call(&Foo::one, 42); 
    wfoo.call(&Foo::two, 32, 3.1415); 
} 
11

Non credo che ci sia un modo generico portatile per farlo nel C++ corrente. Se i template fossero in grado di prendere un overload set come parametro template (che mi piacerebbe molto vedere in C++ 14 per molte ragioni), e il sito di chiamata potrebbe essere cambiato da x.y(z) a x->y(z), penso che probabilmente potrebbe essere fatto con un proxy e un sovraccarico operator->. Altrimenti, il miglior modo generico per fare qualcosa di simile è usare i framework Aspect Oriented Programming per C++ (come AspectC++).

Essere in grado di racchiudere ogni chiamata di funzione membro è solo metà della storia su questo, però. In base al Principio di interfaccia , l'interfaccia di una classe sono le funzioni che menzionano una classe e sono fornite con una classe. Ciò include funzioni membro pubbliche, funzioni amico e funzioni gratuite nello stesso spazio dei nomi della classe. Essere in grado di passare istanze a tali funzioni in un modo avvolto è un problema molto più sottile del semplice avvolgere le chiamate delle funzioni membro, che è l'approccio di Sutter che mostra potenza e flessibilità reali.

+3

+1 per menzionare AOP, che mira proprio a rispondere a questo tipo di domande. –

+7

[Classic Stroustrup paper] (http://www.stroustrup.com/wrapper.pdf) che discute la tecnica proxy + 'operator->' per le chiamate alla funzione membro bracketing. –

0

Per chiunque sia interessato, ho anche scritto un'implementazione generica del eseguire intorno IDOM:

https://github.com/ArnaudBienner/ExecuteAround

https://github.com/ArnaudBienner/ExecuteAround/blob/master/ExecuteAround.h

con un esempio su come fare un oggetto thread-safe da esso: https://github.com/ArnaudBienner/ExecuteAround/blob/master/main.cpp#L78

Per la cronaca, dal momento che quello fornito da Jonathan sembra già grande, e la mia, probabilmente ha bisogno di un po ' pulire.

-1

È interamente possibile ed è stato proposto a lungo non più di Stroustrup e la sua proposta originale è ancora disponibile. Vedi www.stroustrup.com/wrapper.pdf

Fondamentalmente l'idea è di ignorare operatore -> a 2 livelli e blocco/sblocco mutex nel costruttore e distruttore di oggetto provvisorio restituiti dal primo operatore ->.

Il secondo operatore -> restituirà il puntatore dell'oggetto su cui verrà richiamato il metodo.

Problemi correlati