Как обернуть вызовы каждой функции-члена класса в С++ 11?
Herb Sutter задал этот вопрос в разговоре о С++ 11 и concurrency (см. это видео)
Основная идея здесь состоит в том, чтобы иметь класс non-lock X
, где каждый вызов функции должен быть украшен блокировкой, которая разблокирована после функции.
Тем не менее, Херб Саттер затем дрейфует и представляет подход, основанный на функторе. Мне интересно, возможно ли даже с С++ 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";
});
Ответы
Ответ 1
Вопрос о шаблоне 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)
Ответ 2
Я не верю, что в текущем С++ есть переносимый общий способ. Если шаблоны были способны принимать перегрузку в качестве параметра шаблона (что мне очень хотелось бы увидеть на С++ 14 по многим причинам), а сайт вызова можно было бы изменить с x.y(z)
на x->y(z)
, я думаю, что это возможно, возможно, с прокси-сервером и перегруженным operator->
. В противном случае наилучшим общим способом сделать что-то подобное является использование аспектно ориентированных программных сред для С++ (таких как AspectС++).
Возможность переносить вызов каждой функции-члена только на самом деле составляет половину истории. В соответствии с Принципом интерфейса, интерфейс класса - это функции, которые упоминают класс и снабжаются классом. Это включает в себя функции публичных членов, функции друзей и свободные функции в том же пространстве имен, что и класс. Возможность передавать экземпляры таким функциям завернутым способом - это гораздо более тонкая проблема, чем просто перенос вызовов функций-членов, в которых подход Sutter показывает реальную мощность и гибкость.
Ответ 3
Невозможно выполнить именно то, что вы хотите, но что-то близкое выполнимо.
#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);
}
Ответ 4
Для всех, кого это интересует, я также написал общую реализацию 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
Только для записи, так как тот, который предоставил Джонатан, уже выглядит великолепно, и мне, вероятно, нужно немного очистить.