以下代码是否会导致c ++中的内存泄漏

时间:2008-09-29 04:58:34

标签: c++ exception memory-leaks constructor

class someclass {};

class base
{
    int a;
    int *pint;
    someclass objsomeclass;
    someclass* psomeclass;
public:
    base()
    {
        objsomeclass = someclass();
        psomeclass = new someclass();
        pint = new int(); 
        throw "constructor failed";
        a = 43;
    }
}

int main()
{
    base temp();
}

在上面的代码中,构造函数抛出。哪些对象会被泄露,以及如何避免内存泄漏?

int main()
{
    base *temp = new base();
}

上面的代码怎么样?构造函数抛出后如何避免内存泄漏?

7 个答案:

答案 0 :(得分:36)

是的,它会泄漏内存。当构造函数抛出时,不会调用析构函数(在这种情况下,您不会显示释放动态分配的对象的析构函数,但假设您有一个)。

这是使用智能指针的一个主要原因 - 因为智能助手是完全成熟的对象,他们将在异常堆栈中调用析构函数,并有机会释放内存。

如果你使用类似Boost的scoped_ptr<>模板,你的类看起来更像:

class base{
    int a;
    scoped_ptr<int> pint;
    someclass objsomeclass;
    scoped_ptr<someclass> psomeclass;
    base() : 
       pint( new int),
       objsomeclass( someclass()),
       psomeclass( new someclass())

    {
        throw "constructor failed";
        a = 43;
    }
}

并且你没有内存泄漏(默认的dtor也会清理动态内存分配)。


总结(希望这也回答了关于

的问题
base* temp = new base();

语句):

当在构造函数中抛出异常时,在正确处理可能在对象的中止构造中发生的资源分配方面,您应该注意以下几点:

  1. 正在构造的对象的析构函数将被调用。
  2. 该对象类中包含的成员对象的析构函数将被称为
  3. 将释放正在构建的对象的内存。
  4. 这意味着如果您的对象拥有资源,您可以使用2种方法来清理构造函数抛出时可能已经获取的资源:

    1. 捕获异常,释放资源,然后重新抛出。这可能很难纠正并且可能成为维护问题。
    2. 使用对象来管理资源生命周期(RAII)并将这些对象用作成员。当对象的构造函数抛出异常时,成员对象将调用析构函数,并有机会释放他们负责的生命周期的资源。

答案 1 :(得分:5)

两个新人都将被泄露。

将堆创建的对象的地址分配给命名的智能指针,以便在抛出异常时获取调用的智能指针析构函数中删除它 - (RAII)。

class base {
    int a;
    boost::shared_ptr<int> pint;
    someclass objsomeclass;
    boost::shared_ptr<someclass> psomeclass;

    base() :
        objsomeclass( someclass() ),
        boost::shared_ptr<someclass> psomeclass( new someclass() ),
        boost::shared_ptr<int> pint( new int() )
    {
        throw "constructor failed";
        a = 43;
    }
};

现在 psomeclass &amp;当构造函数中抛出异常时,当堆栈展开时,将调用 pint 析构函数,并且这些析构函数将释放已分配的内存。

int main(){
    base *temp = new base();
}

对于使用(non-plcaement)new的普通内存分配,如果构造函数抛出异常,则由operator new分配的内存将自动释放。就为什么要解放个别成员而言(响应对Mike B的回答的评论),自动释放仅适用于在新分配的对象的构造函数中抛出异常,而不是在其他情况下。此外,释放的内存是为对象成员分配的内存,而不是您在构造函数中分配的任何内存。即它将释放成员变量 a pint objsomeclass psomeclass 的内存,但不是从 new someclass() new int()分配的内存。

答案 2 :(得分:1)

我认为最重要的答案是错误的,但仍然会泄漏内存。 如果构造函数抛出异常(因为它从未完成其初始化,并且可能某些成员从未到过构造函数调用),则类成员的析构函数将 not 被调用。 他们的析构函数只在类的析构函数调用期间调用。这才有意义。

这个简单的程序演示了它。

#include <stdio.h>


class A
{
    int x;

public:
    A(int x) : x(x) { printf("A constructor [%d]\n", x); }
    ~A() { printf("A destructor [%d]\n", x); }
};


class B
{
    A a1;
    A a2;

public:
    B()
    :   a1(3),
        a2(5)
    {
        printf("B constructor\n");
        throw "failed";
    }
    ~B() { printf("B destructor\n"); }
};


int main()
{
    B b;

    return 0;
}

使用以下输出(使用g ++ 4.5.2):

A constructor [3]
A constructor [5]
B constructor
terminate called after throwing an instance of 'char const*'
Aborted

如果您的构造函数失败了,那么您有责任处理它。更糟糕的是,可能会从基类的构造函数中抛出异常! 处理这些情况的方法是使用“函数try块”(但即使这样,你也必须仔细编译部分初始化对象的破坏)。

正确解决问题的方法是:

#include <stdio.h>


class A
{
    int x;

public:
    A(int x) : x(x) { printf("A constructor [%d]\n", x); }
    ~A() { printf("A destructor [%d]\n", x); }
};


class B
{
    A * a1;
    A * a2;

public:
    B()
    try  // <--- Notice this change
    :   a1(NULL),
        a2(NULL)
    {
        printf("B constructor\n");
        a1 = new A(3);
        throw "fail";
        a2 = new A(5);
    }
    catch ( ... ) {   // <--- Notice this change
        printf("B Cleanup\n");
        delete a2;  // It's ok if it's NULL.
        delete a1;  // It's ok if it's NULL.
    }

    ~B() { printf("B destructor\n"); }
};


int main()
{
    B b;

    return 0;
}

如果你运行它,你将获得预期的输出,其中只有被分配的对象被销毁和释放。

B constructor
A constructor [3]
B Cleanup
A destructor [3]
terminate called after throwing an instance of 'char const*'
Aborted

如果您愿意,还可以使用智能共享指针进行额外复制。编写类似于此的构造函数:

class C
{
    std::shared_ptr<someclass> a1;
    std::shared_ptr<someclass> a2;

public:
    C()
    {
        std::shared_ptr<someclass> new_a1(new someclass());
        std::shared_ptr<someclass> new_a2(new someclass());

        // You will reach here only if both allocations succeeded. Exception will free them both since they were allocated as automatic variables on the stack.
        a1 = new_a1;
        a2 = new_a2;
    }
}
祝你好运, Tzvi。

答案 3 :(得分:0)

是的,该代码会泄漏内存。引发异常时,不会释放使用“new”分配的内存块。这是RAII背后动机的一部分。

为避免内存泄漏,请尝试以下方法:

psomeclass = NULL;
pint = NULL;
/* So on for any pointers you allocate */

try {
    objsomeclass = someclass();
    psomeclass = new someclass();
    pint = new int(); 
    throw "constructor failed";
    a = 43;
 }
 catch (...)
 {
     delete psomeclass;
     delete pint;
     throw;
 }

答案 4 :(得分:0)

如果你引入一个构造函数,你应该清理在throw之前的所有内容。如果你使用继承或抛出析构函数,你真的不应该。行为是奇怪的(没有我的标准方便,但它可能是未定义的?)。

答案 5 :(得分:-2)

您需要删除所有“新”内容,否则会导致内存泄漏。所以这两行:

psomeclass = new someclass();
pint = new int(); 

会导致内存泄漏,因为您需要这样做:

delete pint;
delete psomeclass;

在最后一个块中,以避免它们被泄露。

此外,这一行:

base temp = base();

没必要。你只需要这样做:

base temp;

不需要添加“= base()”。

答案 6 :(得分:-3)

你需要删除psomeclass ...没有必要清理整数......

RWendi