Как обернуть вызовы каждой функции-члена класса в 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 уровнях и блокировка / разблокировка мьютекса в конструкторе и деструкторе временного объекта, который возвращается первым оператором ->,

Второй оператор -> вернет указатель объекта, для которого будет вызван метод.

Другие вопросы по тегам