Будет ли приведенный ниже код причиной утечки памяти в C ++ - PullRequest
18 голосов
/ 29 сентября 2008
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 ]

36 голосов
/ 29 сентября 2008

Да, утечка памяти. Когда конструктор сгенерирует, деструктор не будет вызван (в этом случае вы не показываете деструктор, который освобождает динамически размещенные объекты, но давайте предположим, что он у вас есть).

Это основная причина для использования умных указателей - поскольку умные poitners являются полноценными объектами, они будут вызывать деструкторы во время разматывания стека исключения и имеют возможность освободить память.

Если вы используете что-то вроде шаблона Boost's 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. память для создаваемого объекта будет освобождена.

Это означает, что если вашему объекту принадлежат ресурсы, у вас есть 2 метода для очистки тех ресурсов, которые могли быть уже получены, когда конструктор выдает:

  1. поймать исключение, освободить ресурсы, а затем сбросить. Это может быть трудно исправить, и это может стать проблемой обслуживания.
  2. использовать объекты для управления временем жизни ресурса (RAII) и использовать эти объекты в качестве членов. Когда конструктор для вашего объекта генерирует исключение, объектам-членам будут вызываться дескрипторы, и у него будет возможность освободить ресурс, за время жизни которого они ответственны.
5 голосов
/ 29 сентября 2008

Обе новые утечки будут.

Назначьте адрес созданных объектов кучи для с именем интеллектуальных указателей, чтобы он был удален в деструкторе интеллектуальных указателей, которые получают вызов при возникновении исключения - ( 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 & pint будут вызываться деструкторы, когда стек разматывается при возникновении исключения в конструкторе, и эти деструкторы освобождают выделенную память.

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

Для обычного выделения памяти с использованием (non-plcaement) new память, выделенная оператором new, освобождается автоматически, если конструктор выдает исключение. С точки зрения причин освобождения отдельных членов (в ответ на комментарии к ответу Майка Б) автоматическое освобождение применяется только в том случае, если в конструктор объекта, который был недавно выделен, было сгенерировано исключение, а не в других случаях. Кроме того, освобождается память, выделенная для членов объекта, а не память, которую вы могли бы выделить, скажем, внутри конструктора. т.е. он освободил бы память для переменных-членов a , pint , objsomeclass и psomeclass , но не память, выделенная из new someclass () и new int () .

1 голос
/ 28 января 2012

Я считаю, что верхний ответ неверен и все равно будет утечка памяти. Деструктор для членов класса будет не вызываться, если конструктор выдает исключение (потому что он никогда не завершал свою инициализацию и, возможно, некоторые члены никогда не достигали своих вызовов конструктора). Их деструкторы вызываются только во время вызова деструктора класса. Это имеет смысл только.

Эта простая программа демонстрирует это.

#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

Если ваш конструктор потерпел неудачу на полпути, то вы несете ответственность за его устранение. Хуже того, исключение может быть сгенерировано из конструктора вашего базового класса! Чтобы справиться с этими случаями, нужно использовать «блок попытки выполнения функции» (но даже тогда вы должны тщательно кодировать уничтожение частично инициализированного объекта).

Тогда правильный подход к вашей проблеме будет примерно таким:

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

Удачи, Цви.

0 голосов
/ 29 сентября 2008

вам нужно удалить psomeclass ... Нет необходимости очищать целое число ...

RWendi

0 голосов
/ 29 сентября 2008

Если вы добавляете конструктор, вы должны очистить все, что было перед вызовом throw. Если вы используете наследование или добавляете деструктор, вы действительно не должны этого делать. Поведение странное (у меня нет стандартного подручного, но оно может быть неопределенным?).

0 голосов
/ 29 сентября 2008

Да, этот код будет пропускать память. Блоки памяти, выделенные с использованием «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;
 }

0 голосов
/ 29 сентября 2008

Все, что вы «новый», необходимо удалить, иначе вы вызовете утечку памяти. Итак, эти две строки:

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

Будет причиной утечки памяти, потому что вам нужно сделать:

delete pint;
delete psomeclass;

В последнем блоке, чтобы избежать их утечки.

Также эта строка:

base temp = base();

Нет необходимости. Вам просто нужно сделать:

base temp;

Добавление "= base ()" не требуется.

...