RAII范围内的分配

Assignment within RAII scope

本文关键字:分配 范围内 RAII      更新时间:2023-10-16

问题

如何在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 s必须与GIL一起创建,而线程则必须

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

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

从我的角度来看,这里有一个完整的选项列表。optional将是我要做的:

所提出的后C++1y optional将解决您的问题,因为它允许您在声明后构造数据,基于堆的unique_ptr解决方案也是如此。滚你自己的,或从boost 偷ot

一个"在作用域结束时运行"RAII函数存储程序(带有"提交"(也可以让这个代码不那么疯狂,让你的锁在它们的作用域内手动脱离也可以。

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锁定类具有move构造函数将允许您在另一个范围中进行构造,并通过move返回(可能被省略(。

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

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

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

这种方法的优点是避免了指针,而且由于省略了复制,所以速度应该很快。

一个缺点是LockedObject不一定支持复制(在这种情况下,use_locked会引用(。