2013-05-31 25 views
19

香草萨特问谈谈这个问题,关于C++ 11和并发性(见this video如何在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"; 
}); 
+0

至少对于某些编译器(例如gcc),编译器可以在不修改代码的情况下执行此操作。通常用于分析,但您可以让它在每个函数调用之前和之后插入对指定函数的调用。对于代码来解决你想要锁定的地方以及你真正没有的地方,这仍然是不重要的。 –

+0

@JerryCoffin我想这对每个维护人员来说都是致命的,因为他们不得不在代码中查找锁。 – Alex

+0

@Alex很好的问题。我记得当Sutter在C++和Beyond 2012中引用了这个观点时,然后就像你说的那样偏离了。也许他在暗示C++ 14功能集。 –

回答

15

问题是关于EXECUTE-AROUND模式。我在https://gitlab.com/redistd/redistd/blob/master/include/redi/exec_around.h

由通用(但只能勉强测试)执行EXECUTE复飞指针这样:

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

更深入的解释上的家庭如何进行以模式工作可以发现here (pdf)

+0

我不确定这是否是我正在寻找的东西,但它已经让我兴奋了! – Alex

+0

+1哈,对! 'operator->'的链接行为!很好的解决方案。 –

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); 
} 
11

我不相信在当前的C++中有一个可移植的通用方法来做到这一点。如果模板能够将重载设置为模板参数(我很想在C++ 14中看到它,原因很多),并且调用站点可以从x.y(z)更改为x->y(z),我认为它可能可能用代理完成并且超载operator->。否则,像这样做的最好的通用方法就是使用面向C++的面向方面编程框架(如AspectC++)。尽管如此,能够包装每个成员函数的调用实际上只是这个故事的一半。根据接口原理,一个类的接口是提及一个类并提供一个类的函数。这包括公共成员函数,朋友函数和与类相同名称空间中的免费函数。能够以包装的方式将实例传递给这些函数是一个比仅仅包装成员函数调用更加微妙的问题,这是Sutter的方法显示出真正的能力和灵活性的地方。

+3

+1提到AOP,其目的正是为了回答这类问题。 –

+7

[Classic Stroustrup论文](http://www.stroustrup.com/wrapper.pdf),讨论了用于包围成员函数调用的代理+操作员 - >技术。 –

-1

这是完全可能的,它被建议早已远离斯特劳斯特鲁普,他的原始提案仍然可用。见www.stroustrup.com/wrapper.pdf

基本上想法是在2级和锁定/解锁互斥在构造和临时对象的析构函数是由第一运营商->返回重写操作者->

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

相关问题