Подтвердить что ты не робот

Как обернуть вызовы каждой функции-члена класса в С++ 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";
});
4b9b3361

Ответ 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

Только для записи, так как тот, который предоставил Джонатан, уже выглядит великолепно, и мне, вероятно, нужно немного очистить.