poo - ¿Cómo ajustar las llamadas de cada función miembro de una clase en C++ 11?
operador de resolucion de ambito c++ (5)
Es totalmente posible y fue propuesto hace mucho tiempo por nada menos que Stroustrup y su propuesta original todavía está disponible. Consulte www.stroustrup.com/wrapper.pdf
Básicamente, la idea es anular el operador ->
en 2 niveles y bloquear / desbloquear la exclusión mutua en el constructor y destructor del objeto temporal que devuelve el primer operador ->
.
El segundo operador ->
devolverá el puntero del objeto sobre el que se invocará el método.
Herb Sutter hizo esta pregunta en una charla sobre C ++ 11 y concurrencia (vea este video )
La idea clave aquí es tener una clase X
sin bloqueo donde cada llamada de función debe estar decorada con una cerradura que se desbloquee después de una función.
Sin embargo, Herb Sutter se desvía y presenta un enfoque basado en funtores. Me pregunto si es posible con C ++ 11 envolver cada llamada de función con el bloqueo y desbloqueo de una clase de una manera genérica (no envolver cada llamada de función manualmente).
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...
};
También existe el patrón decorador.
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;
};
pero hay algo como esto posible:
template<>
class wrap_everything;
wrap_everything<X> x;
x.somefunc(x1,x2,...); // this is then locked.
En aras de la integridad, este es el enfoque basado en el funtor de la hierba Sutter:
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";
});
La pregunta es sobre el patrón EXECUTE-AROUND. Hice una implementación genérica (pero apenas probada) de EXECUTE-AROUND POINTER en https://gitlab.com/redistd/redistd/blob/master/include/redi/exec_around.h
Esto permite:
struct X { void f() { } };
auto x = mutex_around<X>();
x->f(); // locks a mutex for duration of call to X::f
Aquí puede encontrar una explicación más detallada sobre cómo funciona la familia de ejecuciones alrededor de patrones (pdf)
No creo que haya una forma genérica portátil de hacer esto en C ++ actual. Si las plantillas fueran capaces de tomar un conjunto de sobrecarga como un parámetro de plantilla (que me gustaría ver en C ++ 14 por muchas razones), y el sitio de la llamada podría cambiarse de xy(z)
a x->y(z)
, creo que probablemente podría hacerse con un proxy y un operator->
sobrecargado- operator->
. De lo contrario, la mejor forma genérica de hacer algo como esto es usar los marcos de Programación Orientada a Aspectos para C ++ (como AspectC ++).
Sin embargo, ser capaz de envolver cada llamada de función miembro es solo la mitad de la historia. De acuerdo con el principio de interfaz , la interfaz de una clase son las funciones que mencionan una clase y se suministran con una clase. Esto incluye funciones de miembro público, funciones de amigo y funciones gratuitas en el mismo espacio de nombres que la clase. Ser capaz de pasar instancias a tales funciones de una manera envuelta es un problema mucho más sutil que simplemente envolver las llamadas de funciones de los miembros, que es donde el enfoque de Sutter muestra poder y flexibilidad reales.
No es posible hacer exactamente lo que quiere, pero se puede hacer algo cercano.
#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);
}
Para cualquier persona interesada, también escribí una implementación genérica de la ejecución alrededor de idom:
https://github.com/ArnaudBienner/ExecuteAround
https://github.com/ArnaudBienner/ExecuteAround/blob/master/ExecuteAround.h
con un ejemplo sobre cómo hacer un objeto seguro para subprocesos desde él: https://github.com/ArnaudBienner/ExecuteAround/blob/master/main.cpp#L78
Solo para que quede constancia, ya que el proporcionado por Jonathan ya se ve muy bien, y el mío probablemente necesita algo de limpieza.