如何“尝试/最终”操作在C ++中无法使用RAII时? [英] How to do "try/finally" in C++ when RAII is not possible?

查看:82
本文介绍了如何“尝试/最终”操作在C ++中无法使用RAII时?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我从沉重的C#背景回到C ++,并且继承了一些C ++代码库,我认为这可能与最佳C ++实践不符。

I'm coming back to C++ from a heavy C# background and I've inherited some C++ codebase which I think might not have been in line with the best C++ practices.

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

For example, I'm dealing with the following case (simplified):

// 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
 }
}

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

Here ResourceConsumer creates an instance of Resource and does some other things. For some reason (outside my control), it exposes Initialize method for that, rather than offering a non-default constructor, what apparently violates the RAII pattern. It's a library code and the API can't be refactored without making it a breaking change.

所以我的问题是,如何编写 Initialize 在这种情况下正确吗?像下面这样使用步速构造/破坏并重新掷球是否可以接受?就像我说的那样,我来自C#,在这里我只是使用 try / finally using 模式。

So my question is, how to code Initialize correctly in this case? Is it an acceptable practice to use an in-pace construction/destruction and a re-throw, like below? As I said, I came from C# where I'd simply use try/finally or the using pattern for that.

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


推荐答案

制作资源可移动类型。给它移动构造/分配。然后,您的 Initialize 方法如下所示:

Make Resource a moveable type. Give it move construction/assignment. Then, your Initialize method can look like this:

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

请注意,在此示例中,不需要异常处理逻辑。这一切都可以解决。通过首先创建新资源,如果该创建引发异常,则我们保留先前创建的资源(如果有)。这就提供了强有力的异常保证:在发生异常的情况下,对象的状态将完全保留在异常发生之前的状态。

Note that in this example, there is no need for exception handling logic. It all works itself out. By creating the new resource first, if that creation throws an exception, then we keep the previously created resource (if any). That provides the strong exception guarantee: in the event of an exception, the state of the object is preserved exactly as it was before the exception.

并且请注意,没有异常需要明确的 try catch 块。 RAII才有效。

And note that there is no need for explicit try and catch blocks. RAII just works.

您的资源移动操作如下:

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

这篇关于如何“尝试/最终”操作在C ++中无法使用RAII时?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆