RAII范围内的转让

时间:2014-06-29 16:03:11

标签: c++ c++11 locking raii

问题

如何初始化RAII范围内的对象,并在该范围之外使用它?

背景

  • 我有一个全局锁,可以使用lock()unlock()来调用。

  • 我有一个LockedObject类型,只有在全局锁被锁定时才能初始化。

  • 我有一个函数use_locked(LockedObject &locked_object),需要在解锁全局锁的情况下调用它。

使用方案是

lock();
LockedObject locked_object;
unlock();
use_locked(locked_object);

RAII

出于各种原因,我转向了全局锁的RAII封装。我想在任何地方使用它,主要是因为创建LockedObject可能会因异常而失败。

问题在于

{
    GlobalLock global_lock;
    LockedObject locked_object;
}
use_locked(locked_object);

失败,因为在内部范围中创建了locked_object

实施例

设置(主要不重要):

#include <assert.h> 
#include <iostream>

bool locked = false;

void lock() {
    assert(!locked);
    locked = true;  
}

void unlock() {
    assert(locked);
    locked = false;
}

class LockedObject {
    public:
        LockedObject(int i) {
            assert(locked);
            std::cout << "Initialized: " << i << std::endl;
        }
};

void use_locked(LockedObject locked_object) {
    assert(!locked);
}

class GlobalLock {
    public:
        GlobalLock() {
            lock();
        }

        ~GlobalLock() {
            unlock();
        }
};

原始的非RAII方法:

void manual() {
    lock();
    LockedObject locked_object(123);
    unlock();
    use_locked(locked_object);
}

破坏RAII方法:

/*
void raii_broken_scoping() {
    {
        GlobalLock global_lock;

        // Initialized in the wrong scope
        LockedObject locked_object(123);
    }
    use_locked(locked_object);
}
*/

/*
void raii_broken_initialization() {
    // No empty initialization
    // Alternatively, empty initialization requires lock
    LockedObject locked_object;
    {
        GlobalLock global_lock;
        locked_object = LockedObject(123);
    }
    use_locked(locked_object);
}
*/

main函数:

int main(int, char **) {
    manual();
    // raii_broken_scoping();
    // raii_broken_initialization;
}

对于它的价值,在Python中我会这样做:

with GlobalLock():
    locked_object = LockedObject(123)

我想要相当于那个。我在答案中提到了我目前的解决方案,但感觉很笨拙。


要执行的特定(但简化)代码如下。使用我当前基于lambda的调用:

boost::python::api::object wrapped_object = [&c_object] () {
    GIL lock_gil;
    return boost::python::api::object(boost::ref(c_object));
} ();

auto thread = std::thread(use_wrapped_object, c_object);

class GIL {
    public:
        GIL();
        ~GIL();

    private:
        GIL(const GIL&);
        PyGILState_STATE gilstate;
};
GIL::GIL() {
    gilstate = PyGILState_Ensure();
}

GIL::~GIL() {
    PyGILState_Release(gilstate);
}

boost::python::api::object 必须使用GIL创建,并且必须创建没有GIL的线程。 PyGILState结构和函数调用都是由CPython的C API给我的,所以我只能将它们包装起来。

3 个答案:

答案 0 :(得分:3)

在堆上分配您的对象并使用一些指针:

std::unique_ptr<LockedObject> locked_object;
{
    GlobalLock global_lock;
    locked_object.reset(new LockedObject());
}
use_locked(locked_object);

答案 1 :(得分:2)

从我的角度来看,这是一个完整的选项列表。 optional就是我要做的事情:

建议的后C ++ 1y optional将解决您的问题,因为它允许您在声明后构造数据,就像基于堆的unique_ptr解决方案一样。滚动自己,或从boost

偷走

A&#39;在范围结束时运行&#39; RAII函数存储器(使用&#39; commit&#39;)也可以使这个代码不那么疯狂,因为可以让你的锁在他们的范围内手动解除。

template<class F>
struct run_at_end_of_scope {
  F f;
  bool Skip;
  void commit(){ if (!Skip) f(); Skip = true; }
  void skip() { Skip = true; }
  ~run_at_end_of_scope(){commit();}
};
template<class F>
run_at_end_of_scope<F> at_end(F&&f){ return {std::forward<F>(f), false}; }

然后:

auto later = at_end([&]{ /*code*/ });

您可以later.commit();later.skip();提前运行代码或跳过运行代码。

使你的RAII锁定类具有移动构造函数可以让你在另一个作用域中构建,并通过移动返回(可能被省略)。

LockedObject make_LockedObject(){
  GlobalLock lock;
  return {};
}

答案 2 :(得分:0)

我目前的解决方案是使用匿名函数:

void raii_return() {
    LockedObject locked_object = [&] () {
        GlobalLock global_lock;
        return LockedObject(123);
    } ();
    use_locked(locked_object);
}

这种方法的优点是它避免了指针,并且由于copy elision它应该非常快。

一个缺点是LockedObject不一定支持复制(在这种情况下use_locked会参考)。