24

Herb Sutter 在关于 C++11 和并发的演讲中提出了这个问题(请参阅此视频

这里的关键思想是有一个非锁定类X,其中每个函数调用都应该用一个在函数之后解锁的锁来装饰。

然而,Herb Sutter 又偏离了方向,提出了一种基于函子的方法。我想知道是否甚至可以使用 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";
});
4

5 回答 5

17

问题是关于 EXECUTE-AROUND 模式。我在https://gitlab.com/redistd/redistd/blob/master/include/redi/exec_around.h上做了一个通用的(但几乎没有测试过)EXECUTE-AROUND POINTER 实现

这允许:

struct X { void f() { } };
auto x = mutex_around<X>();
x->f();  // locks a mutex for duration of call to X::f

可以在此处找到有关执行模式系列如何工作的更深入的解释(pdf)

于 2013-06-01T00:22:06.530 回答
11

我不相信在当前的 C++ 中有一种可移植的通用方法来做到这一点。如果模板能够将重载集作为模板参数(出于多种原因,我非常希望在 C++14 中看到),并且调用站点可以从 更改x.y(z)x->y(z),我认为它可能会完成带有代理和重载的operator->. 否则,执行此类操作的最佳通用方法是使用 C++ 的面向方面编程框架(例如 AspectC++)。

不过,能够包装每个成员函数调用实际上只是故事的一半。根据接口原理,类的接口是指一个类并由一个类提供的函数。这包括与类在同一命名空间中的公共成员函数、友元函数和自由函数。能够以包装的方式将实例传递给此类函数是一个比仅仅包装成员函数调用更微妙的问题,这就是 Sutter 的方法显示真正强大和灵活性的地方。

于 2013-05-31T14:50:35.720 回答
6

不可能完全按照您的意愿去做,但接近的事情是可行的。

#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);
}
于 2013-05-31T14:49:11.003 回答
1

对于任何感兴趣的人,我还编写了一个围绕 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

仅作记录,因为乔纳森提供的那个已经看起来很棒,我的可能需要一些清理。

于 2017-03-09T13:16:23.027 回答
0

这是完全有可能的,早在 Stroustrup 就提出了这个建议,他最初的建议仍然可用。见www.stroustrup.com/wrapper.pdf

基本上,这个想法是->在第一个操作符返回的临时对象的构造函数和析构函数中覆盖 2 个级别的运算符并锁定/解锁互斥锁->

第二个运算符->将返回调用方法的对象的指针。

于 2018-02-26T09:55:04.543 回答