当RAII无法实现时,如何在C++中"尝试/最后"?

avo*_*avo 2 c++ constructor destructor exception raii

我从重C#背景回到C++,我继承了一些C++代码库,我认为这些代码库可能不符合最好的C++实践.

例如,我正在处理以下情况(简化):

// resource
class Resource {
 HANDLE _resource = NULL;
 // copying not allowed
 Resource(const Resource&); 
 Resource& operator=(const Resource& other);
public:
 Resource(std::string name) { 
  _resource = ::GetResource(name); if (NULL == _resource) throw "Error"; }
 ~Resource() {
  if (_resource != NULL) { CloseHandle(_resource); _resource = NULL; };
 }
 operator HANDLE() const { return _resource; }
};

// resource consumer
class ResourceConsumer {
 Resource _resource;
 // ...
 public:
  void Initialize(std::string name) {
   // initialize the resource
   // ...
   // do other things which may throw
 }
}
Run Code Online (Sandbox Code Playgroud)

这里ResourceConsumer创建一个实例Resource并执行其他一些操作.由于某种原因(在我的控制之外),它暴露Initialize了该方法,而不是提供非默认构造函数,显然违反了RAII模式.这是一个库代码,如果不进行重大修改,API就无法重构.

所以我的问题是,Initialize在这种情况下如何正确编码?使用步调建筑/破坏和重新投掷是否是可接受的做法,如下所示?正如我所说,我来自C#,我只是使用try/finallyusing模式.

 void ResourceConsumer::Initialize(std::string name) {
  // first destroy _resource in-place      
  _resource.~Resource();
  // then construct it in-place
  new (&_resource) Resource(name);
  try {
    // do other things which may throw
    // ...
  }
  catch {
    // we don't want to leave _resource initialized if anything goes wrong
    _resource.~Resource();   
    throw;
  }
}
Run Code Online (Sandbox Code Playgroud)

Nic*_*las 8

制作Resource一个可移动的类型.给它移动施工/任务.然后,您的Initialize方法可能如下所示:

void ResourceConsumer::Initialize(std::string name)
{
    //Create the resource *first*.
    Resource res(name);

    //Move the newly-created resource into the current one.
    _resource = std::move(res);
}
Run Code Online (Sandbox Code Playgroud)

请注意,在此示例中,不需要异常处理逻辑.这一切都有效.通过首先创建新资源,如果该创建抛出异常,那么我们保留先前创建的资源(如果有的话).这提供了强大的异常保证:在异常的情况下,对象的状态保持与异常之前完全相同.

请注意,不需要显式trycatch块.RAII就行了.

你的Resource移动操作将是这样的:

class Resource {
public:
    Resource() = default;

    Resource(std::string name) : _resource(::GetResource(name))
    {
        if(_resource == NULL) throw "Error";
    }

    Resource(Resource &&res) noexcept : _resource(res._resource)
    {
        res._resource = NULL;
    }

    Resource &operator=(Resource &&res) noexcept
    {
        if(&res != this)
        {
            reset();
            _resource = res._resource;
            res._resource = NULL;
        }
    }

    ~Resource()
    {
        reset();
     }

    operator HANDLE() const { return _resource; }

private:
    HANDLE _resource = NULL;

    void reset() noexcept
    {
        if (_resource != NULL)
        {
            CloseHandle(_resource);
            _resource = NULL;
        }
    }
};
Run Code Online (Sandbox Code Playgroud)

  • 我已经编辑了自己的答案,以明确思考过程缺乏,并且这个答案是转到的地址.双关语不打算:) (2认同)