C++ 中finally 的实现

发布于 2024-07-10 09:13:41 字数 851 浏览 8 评论 0原文

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

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. 对象没有抛出异常 -> 无异常-> 对象已清理
  2. 对象引发的异常 -> 已处理-> 无异常-> 对象已清理
  3. 对象引发的异常 -> 抛出-> 异常-> 对象已清理 -> 抛出

Is this a good way to implement a Finally-like behavior in standard C++?
(Without special pointers)

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. No exception thrown by object -> NoException -> Object cleaned up
  2. Exception thrown by object -> Handled -> NoException -> Object cleaned up
  3. Exception thrown by object -> Thrown -> Exception -> Object cleaned up -> Thrown

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

小糖芽 2024-07-17 09:13:41

标准答案是使用 resource-allocation-is-initialization 缩写 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,在这种情况下,资源分配是为对象分配内存并构造它,初始化是 unique_ptr 的初始化。

The standard answer is to use some variant of resource-allocation-is-initialization abbreviated RAII. Basically you construct a variable that has the same scope as the block that would be inside the block before the finally, then do the work in the finally block inside the objects destructor.

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

becomes

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

Cleanup cleanupObj;

// Some work.

This looks terribly inconvenient, but usually there's a pre-existing object that will do the clean up for you. In your case, it looks like you want to destruct the object in the finally block, which means a smart or unique pointer will do what you want:

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

or modern C++

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

No matter which exceptions are thrown, the object will be destructed. Getting back to RAII, in this case the resource allocation is allocating the memory for the Object and constructing it and the initialization is the initialization of the unique_ptr.

别靠近我心 2024-07-17 09:13:41

不。构建最终类似方式的标准方法是分离关注点(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...

No. The Standard way to build a finally like way is to separate the concerns (http://en.wikipedia.org/wiki/Separation_of_concerns) and make objects that are used within the try block automatically release resources in their destructor (called "Scope Bound Resource Management"). Since destructors run deterministically, unlike in Java, you can rely on them to clean up safely. This way the objects that aquired the resource will also clean up the resource.

One way that is special is dynamic memory allocation. Since you are the one aquiring the resource, you have to clean up again. Here, smart pointers can be used.

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...
梨涡 2024-07-17 09:13:41

如果由于某种奇怪的原因您无法访问标准库,那么很容易实现您需要的智能指针类型来处理资源。 它可能看起来有点冗长,但它的代码比那些嵌套的 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”析构函数保证在这两种情况下都会执行(尽管如果程序终止则不会执行)。

If for some strange reason you don't have access to the standard libraries, then it's very easy to implement as much as you need of a smart pointer type to handle the resource. It may look a little verbose, but it's less code than those nested try/catch blocks, and you only have to define this template once ever, instead of once per resource that needs management:

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

Of course, if you do this and then use RAII in the rest of your code, you'll eventually end up needing all the features of the standard and boost smart pointer types. But this is a start, and does what I think you want.

The try ... catch approach probably won't work well in the face of maintenance programming. The CLEAN UP block isn't guaranteed to be executed: for example if the "do something" code returns early, or somehow throws something which is not an Exception. On the other hand, the destructor of "deleter" in my code is guaranteed to be executed in both those cases (although not if the program terminates).

弱骨蛰伏 2024-07-17 09:13:41

我的建议是:不要尝试模仿 C++ 中 try-finally 子句的行为。 只需使用 RAII 即可。 你会生活得更幸福。

My advice is: don't try to emulate the behaviour of a try-finally clause in C++. Just use RAII instead. You'll live happier.

滥情稳全场 2024-07-17 09:13:41

假设您希望删除指针 myObject 并避免内存泄漏,如果代码中有“return”语句,其中您说 // Do some 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() 用于将指针从一个智能指针传输到另一个智能指针。

Assuming you are looking to delete the pointer myObject and avoid memory leaks, your code can still fail to do this if there is a "return" statement in the code where you say // Do something with myObject. (I am assuming real code would be here)

RAII techniques have the relevant action that is equivalent to a "finally" block, in a particular object's destructor:

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

I think I have the semantics correct; I haven't used shared_ptr much myself, but I prefer it to auto_ptr<> -- a pointer to an object can only be "owned" by one auto_ptr<>. I've used COM's CComPtr and a variant of it that I've written myself for "regular" (non-COM) objects that is similar to shared_ptr<> but has Attach() and Detach() for transfer of pointers from one smart pointer to another.

心在旅行 2024-07-17 09:13:41

直接回答你的问题,

这是实现该功能的巧妙方法,但并不可靠。 一种会让你失败的方法是,如果你的“做某事”代码抛出一个不是从 Exception 派生的异常。 在这种情况下,您将永远不会删除 myObject

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

To directly answer your question, no.

It's a clever way to implement that functionality, but it is not reliable. One way that will fail you is if your "do something" code throws an exception that is not derived from Exception. In that case, you will never delete myObject.

There's a more important issue at hand here, and that's the methodologies adopted by programmers of any particular language. The reason you're hearing about RAII is because programmers with much more experience than you or I have found that in the domain of C++ programming, that methodology is reliable. You can rely on other programmers using it and other programmers will want to rely on you using it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文