Как обернуть вызовы каждой функции-члена класса в C++11?
Херб Саттер задал этот вопрос в беседе о C++11 и параллелизме (см. Это видео)
Ключевой идеей здесь является наличие неблокирующего класса X
где каждый вызов функции должен быть украшен блокировкой, которая разблокируется после функции.
Тем не менее, Херб Саттер отходит и представляет подход, основанный на функторе. Мне интересно, возможно ли даже в C++11 обернуть каждый вызов функции блокировкой и разблокировкой класса универсальным способом (не оборачивая каждый вызов функции вручную).
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...
};
есть также шаблон декоратора
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;
};
но возможно ли что-то подобное:
template<>
class wrap_everything;
wrap_everything<X> x;
x.somefunc(x1,x2,...); // this is then locked.
для полноты, это подход, основанный на функторе травосборника:
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";
});
5 ответов
Вопрос о шаблоне EXECUTE-AROUND. Я сделал общую (но только немного протестированную) реализацию EXECUTE-AROUND POINTER по адресу https://gitlab.com/redistd/redistd/blob/master/include/redi/exec_around.h
Это позволяет:
struct X { void f() { } };
auto x = mutex_around<X>();
x->f(); // locks a mutex for duration of call to X::f
Более подробное объяснение того, как работает семейство исполнителей вокруг шаблонов, можно найти здесь (pdf).
Я не верю, что в нынешнем C++ есть универсальный способ сделать это. Если бы шаблоны могли принимать набор перегрузки в качестве параметра шаблона (что я очень хотел бы видеть в C++14 по многим причинам), и сайт вызова можно было бы изменить с x.y(z)
в x->y(z)
Я думаю, что это может быть сделано с прокси и перегружен operator->
, В противном случае лучший общий способ сделать что-то подобное - это использовать Aspect Oriented Programming frameworks для C++ (например, AspectC++).
Однако возможность обернуть каждый вызов функции-члена - это только половина истории. Согласно принципу интерфейса, интерфейс класса - это функции, которые упоминают класс и поставляются вместе с классом. Сюда входят общедоступные функции-члены, функции-друзья и свободные функции в том же пространстве имен, что и класс. Возможность передавать экземпляры таким функциям в обертке - гораздо более тонкая проблема, чем просто обертка вызовов функций-членов, в которой подход Саттера демонстрирует реальную силу и гибкость.
Невозможно сделать именно то, что вы хотите, но что-то близкое выполнимо.
#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);
}
Для тех, кто заинтересован, я также написал общую реализацию execute вокруг idom:
https://github.com/ArnaudBienner/ExecuteAround
https://github.com/ArnaudBienner/ExecuteAround/blob/master/ExecuteAround.h
с примером того, как сделать из него потокобезопасный объект: https://github.com/ArnaudBienner/ExecuteAround/blob/master/main.cpp#L78
Просто для записи, так как тот, который предоставил Джонатан, уже выглядит великолепно, и мой, вероятно, нуждается в некоторой очистке.
Это вполне возможно, и это было предложено давно ни кем иным, как Страуструпом, и его первоначальное предложение все еще доступно. Смотрите http://www.stroustrup.com/wrapper.pdf
В основном идея состоит в том, чтобы переопределить оператор ->
на 2 уровнях и блокировка / разблокировка мьютекса в конструкторе и деструкторе временного объекта, который возвращается первым оператором ->
,
Второй оператор ->
вернет указатель объекта, для которого будет вызван метод.