17

这是在标准 C++ 中实现类似最终行为的好方法吗?(无特殊指针)

class Exception : public Exception
    { public: virtual bool isException() { return true; } };

class NoException : public Exception
    { public: bool isException() { return false; } };


Object *myObject = 0;

try
{
  // OBJECT CREATION AND PROCESSING
  try
  {
    myObject = new Object();

    // Do something with myObject.
  }

  // EXCEPTION HANDLING
  catch (Exception &e)
  {
    // When there is an excepion, handle or throw,
    // else NoException will be thrown.
  }

  throw NoException();
}

// CLEAN UP
catch (Exception &e)
{
  delete myObject;

  if (e.isException()) throw e;
}
  1. 对象没有抛出异常 -> NoException -> 对象已清理
  2. 对象抛出的异常 -> 已处理 -> NoException -> 对象已清理
  3. 对象抛出的异常 -> 抛出 -> 异常 -> 对象清理 -> 抛出
4

6 回答 6

34

标准答案是使用资源分配即初始化缩写 RAII 的一些变体。基本上,您构造一个变量,该变量与finally之前块内的块具有相同的范围,然后在对象析构函数内的finally块中执行工作。

try {
   // Some work
}
finally {
   // Cleanup code
}

变成

class Cleanup
{
public:
    ~Cleanup()
    {
        // Cleanup code
    }
}

Cleanup cleanupObj;

// Some work.

这看起来非常不方便,但通常有一个预先存在的对象会为您进行清理。在您的情况下,您似乎想要破坏 finally 块中的对象,这意味着智能或唯一指针将执行您想要的操作:

std::unique_ptr<Object> obj(new Object());

或现代 C++

auto obj = std::make_unique<Object>();

无论抛出哪个异常,对象都会被破坏。回到 RAII,在这种情况下,资源分配是为 Object 分配内存并构造它,初始化是 unique_ptr 的初始化。

于 2008-12-24T02:07:37.377 回答
12

不。构建 finally like 方式的标准方法是分离关注点(http://en.wikipedia.org/wiki/Separation_of_concerns)并使 try 块中使用的对象自动释放其析构函数中的资源(称为“范围绑定资源管理”)。由于析构函数是确定性地运行的,与 Java 不同,您可以依靠它们安全地进行清理。这样,获取资源的对象也将清理资源。

一种特殊的方法是动态内存分配。由于您是获取资源的人,因此您必须再次清理。在这里,可以使用智能指针。

try {
    // auto_ptr will release the memory safely upon an exception or normal 
    // flow out of the block. Notice we use the "const auto_ptr idiom".
    // http://www.gotw.ca/publications/using_auto_ptr_effectively.htm
    std::auto_ptr<A> const aptr(new A);
} 
// catch...
于 2008-12-24T02:08:52.353 回答
5

如果由于某种奇怪的原因您无法访问标准库,那么很容易实现您需要的智能指针类型来处理资源。它可能看起来有点冗长,但它的代码比那些嵌套的 try/catch 块要少,而且你只需要定义一次这个模板,而不是每个需要管理的资源一次:

template<typename T>
struct MyDeletable {
    explicit MyDeletable(T *ptr) : ptr_(ptr) { }
    ~MyDeleteable() { delete ptr_; }
private:
    T *ptr_;
    MyDeletable(const MyDeletable &);
    MyDeletable &operator=(const MyDeletable &);
};

void myfunction() {
    // it's generally recommended that these two be done on one line.
    // But it's possible to overdo that, and accidentally write
    // exception-unsafe code if there are multiple parameters involved.
    // So by all means make it a one-liner, but never forget that there are
    // two distinct steps, and the second one must be nothrow.
    Object *myObject = new Object();
    MyDeletable<Object> deleter(myObject);

    // do something with my object

    return;
}

当然,如果您这样做,然后在其余代码中使用 RAII,您最终将需要标准和增强智能指针类型的所有功能。但这是一个开始,做我认为你想要的。

面对维护编程,try ... catch 方法可能不会很好地工作。CLEAN UP 块不能保证被执行:例如,如果“做某事”代码提前返回,或者以某种方式抛出不是异常的东西。另一方面,我的代码中“deleter”的析构函数保证在这两种情况下都会执行(尽管如果程序终止则不会)。

于 2008-12-24T02:44:47.710 回答
5

我的建议是:不要试图模仿 C++ 中 try-finally 子句的行为。只需使用 RAII 代替。你会活得更快乐。

于 2008-12-24T14:14:09.127 回答
3

假设您要删除指针 myObject 并避免内存泄漏,如果您所说的代码中有“return”语句,您的代码仍然无法执行此操作// Do something with myObject.(我假设真正的代码会在这里)

RAII 技术在特定对象的析构函数中具有相当于“finally”块的相关操作:

class ResourceNeedingCleanup
{
  private:
    void cleanup(); // action to run at end
  public:
    ResourceNeedingCleanup( /*args here*/) {}
    ~ResourceNeedingCleanup() { cleanup(); }  

    void MethodThatMightThrowException();
};

typedef boost::shared_ptr<ResourceNeedingCleanup> ResourceNeedingCleanupPtr;
// ref-counted smart pointer


class SomeObjectThatMightKeepReferencesToResources
{
   ResourceNeedingCleanupPtr pR;

   void maybeSaveACopy(ResourceNeedingCleanupPtr& p)
   {
      if ( /* some condition is met */ )
         pR = p;
   }
};

// somewhere else in the code:
void MyFunction(SomeObjectThatMightKeepReferencesToResources& O)
{
   ResourceNeedingCleanup R1( /*parameters*/) ;
   shared_ptr<ResourceNeedingCleanup> pR2 = 
        new ResourceNeedingCleanup( /*parameters*/ );
   try
   {
      R1.MethodThatMightThrowException();
      pR2->MethodThatMightThrowException();
      O->maybeSaveACopy(pR2);
   }
   catch ( /* something */ )
   {
      /* something */
   }

   // when we exit this block, R1 goes out of scope and executes its destructor
   // which calls cleanup() whether or not an exception is thrown.
   // pR2 goes out of scope. This is a shared reference-counted pointer. 
   // If O does not save a copy of pR2, then pR2 will be deleted automatically
   // at this point. Otherwise, pR2 will be deleted automatically whenever
   // O's destructor is called or O releases its ownership of pR2 and the
   // reference count goes to zero.
}

我认为我的语义是正确的;我自己并没有使用多少shared_ptr,但我更喜欢它而不是auto_ptr<>——指向对象的指针只能由一个auto_ptr<>“拥有”。我使用了 COM 的CComPtr及其变体,我自己为“常规”(非 COM)对象编写了类似于 shared_ptr<> 但具有 Attach() 和 Detach() 用于从一个智能传输指针指向另一个的指针。

于 2008-12-24T02:08:10.543 回答
2

直接回答你的问题,

这是实现该功能的一种聪明方法,但它并不可靠。失败的一种方法是,如果您的“做某事”代码抛出了一个不是从Exception. 在那种情况下,你永远不会delete myObject

这里有一个更重要的问题,那就是任何特定语言的程序员采用的方法。您听说RAII的原因是,比您或我经验丰富的程序员发现,在 C++ 编程领域,这种方法是可靠的。您可以依赖其他程序员使用它,而其他程序员也希望依赖您使用它。

于 2008-12-24T02:48:22.990 回答