在构造函数中抛出异常是不安全的?

Mat*_*att 8 c++

我知道从析构函数中抛出异常是不安全的,但是从构造函数中抛出异常是不安全的?

例如,全局声明的对象会发生什么?使用gcc进行快速测试,我得到了中止,是否始终保证?你会用什么解决方案来迎合这种情况?

是否有任何情况下构造函数可以抛出异常而不是留下我们期望的东西.

编辑:我想我应该补充一点,我试图理解在什么情况下我可以获得资源泄漏.看起来明智的做法是手动释放我们在抛出异常之前通过构造获得的资源.我从来不需要在今天之前在构造函数中抛出异常,所以试图理解是否存在任何陷阱.

即这也安全吗?

class P{
  public:
    P() { 
       // do stuff...

       if (error)
          throw exception
    }
}

dostuff(P *p){
 // do something with P
}

... 
try {
  dostuff(new P())
} catch(exception) {

}
Run Code Online (Sandbox Code Playgroud)

分配给对象P的内存是否会被释放?

EDIT2:忘了提到在这种特殊情况下,dostuff将对P的引用存储在输出队列中.P实际上是一条消息,dostuff接收消息,将其路由到适当的输出队列并发送它.基本上,一旦dostuff持有它,它会在后来的dostuff内部释放.我想我想把一个autoptr放在P周围并在dostuff后调用autoptr上的释放以防止内存泄漏,这是正确的吗?

GMa*_*ckG 29

从构造函数中抛出异常是一件好事.当构造函数中的某些内容失败时,您有两个选择:

  • 维持一个"僵尸"状态,该类存在但什么也不做,或者
  • 抛出一个例外.

当真正的答案应该是"这失败了,现在是什么?"时,维护僵尸课程可能会非常麻烦.

根据3.6.2.4的标准:

如果非本地静态对象的构造或破坏以抛出未捕获的异常而结束,则结果是调用terminate(18.6.3.3).

终止指的是std::terminate.


关于你的例子,没有.这是因为您没有使用RAII概念.抛出异常时,堆栈将被展开,这意味着当代码到达最接近的相应catch子句时,所有对象都会调用它们的析构函数.

指针没有析构函数.让我们做一个简单的测试用例:

#include <string>

int main(void)
{
    try
    {
        std::string str = "Blah.";
        int *pi = new int;

        throw;

        delete pi; // cannot be reached
    }
    catch(...)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,str将分配内存,并复制"Blah".进入它,pi并将被初始化为指向内存中的整数.

抛出异常时,堆栈展开开始.它将首先"调用"指针的析构函数(什么也不做),然后str是析构函数,它将释放分配给它的内存.

如果您使用RAII概念,则使用智能指针:

#include <memory>
#include <string>

int main(void)
{
    try
    {
        std::string s = "Blah.";
        std::auto_ptr<int> pi(new int);

        throw;

        // no need to manually delete.
    }
    catch(...)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,pi析构函数将调用delete,不会泄漏任何内存.这就是你应该总是包装指针的原因,这与我们使用的原因相同,std::vector而不是手动分配,调整大小和释放指针.(清洁和安全)

编辑

我忘了提.你问这个:

我想我想把一个autoptr放在P周围并在dostuff后调用autoptr上的释放以防止内存泄漏,这是正确的吗?

我没有明确说明,只是在上面暗示,但答案是否定的.您所要做的就是把它放在里面,auto_ptr当它到来时,它会被自动删除.手动释放它会使首先将其放入容器中的目的失败.

我还建议你看看更高级的智能指针,比如boost中的指针.一个非常受欢迎的shared_ptr,它被引用计数,使其适合存储在容器中并被复制.(不像auto_ptr,难道不能auto_ptr集装箱!)


Mic*_*urr 8

正如Spence所提到的,如果没有仔细编写构造函数来处理该情况,那么从构造函数抛出(或允许异常来转义构造函数)可能会泄漏资源.

这是使用RAII对象(如智能指针)应该受到青睐的一个重要原因 - 它们会在异常情况下自动处理清理.

如果您有需要删除或以其他方式手动释放的资源,则需要确保在异常离开之前清除它们.这并不总是听起来那么容易(当然也不像让RAII对象自动处理它那么容易).

不要忘记,如果你需要手动处理构造函数初始化列表中发生的事情,你需要使用时髦的'function-try-block'语法:

C::C(int ii, double id)
try
     : i(f(ii)), d(id)
{
     //constructor function body
}
catch (...)
{
     //handles exceptions thrown from the ctor-initializer
     //and from the constructor function body
}
Run Code Online (Sandbox Code Playgroud)

此外,请记住,异常安全是"交换"习惯获得广泛青睐的主要原因(唯一的原因) - 这是确保复制构造函数在异常情况下不泄漏或损坏对象的简单方法.

所以,底线是使用异常来处理构造函数中的错误很好,但它不一定是自动的.

  • +1给我看一个我以前没有意识到的毛茸茸的C++语法形式 (3认同)

Mar*_*ork 5

当您从构造函数中抛出异常时,会发生一些事情。

1) 所有完全构造的成员都将调用 thir 析构函数。
2)为对象分配的内存将被释放。

为了帮助使事情自动化,你不应该在你的类中使用 RAW 指针,标准智能指针之一通常会起作用,编译器优化会将大部分开销减少到几乎没有[或者只有你应该手动做的工作反正]。

将指针传递给函数

我不会做的另一件事;是将值作为指针传递给函数。
这里的问题是您没有指出谁拥有该对象。如果没有隐式所有权信息,则不清楚(像所有 C 函数一样)谁负责清理指针。

dostuff(P *p)
{
    // do something with P
}
Run Code Online (Sandbox Code Playgroud)

您提到 p 存储在 que 中并在稍后使用。这意味着您正在将对象的所有权传递给函数。因此,通过使用 std::auto_ptr 使这种关系明确。通过这样做, dostuff() 的调用者知道在调用 dostuff() 之后他不能使用指针,因为调用函数的行为实际上将指针转移到函数中(即调用者本地 auto_ptr 将包含一个 NULL 指针后对 dostuff() 的调用)。

void doStuff(std::auto_ptr<P> p)
{
    // do something with p
    //
    // Even if you do nothing with the RAW pointer you have taken the
    // pointer from the caller. If you do not use p then it will be auto
    // deleted.
}


int main()
{
    // This works fine.
    dostuff(std::auto_ptr<P>(new P));


    // This works just as well.
    std::auto_ptr<P>    value(new P);
    dostuff(value);

    // Here you are guranteed that value has a NULL pointer.
    // Because dostuff() took ownership (and physically took)
    // of the pointer so it could manage the lifespan.
}
Run Code Online (Sandbox Code Playgroud)

在硬币容器中存储指针

您提到 dostuff() 用于存储 p 个对象的列表以进行延迟处理。
所以这意味着您将对象粘贴到容器中。现在普通容器不支持 std::auto_ptr。但是 boost 确实支持指针容器(容器拥有所有权)。这些容器也理解 auto_ptr 并且会自动将所有权从 auto_ptr 转移到容器。

boost::ptr_list<P>   messages;
void doStuff(std::auto_ptr<P> p)
{
    messages.push_front(p);
}
Run Code Online (Sandbox Code Playgroud)

请注意,当您访问这些容器的成员时,它始终返回对所包含对象的引用(而不是指针)。这表明对象的生命周期与容器的生命周期相关,并且只要容器有效,引用就有效(除非您明确删除对象)。