Будет ли приведенный ниже код вызывать утечку памяти в С++?

StackOverflow https://stackoverflow.com/questions/147572

Вопрос

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

А как насчет приведенного выше кода?Как можно избежать утечек памяти после выдачи конструктора?

Это было полезно?

Решение

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

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

Если вы используете что-то вроде шаблона Scod_ptr<> Boost, ваш класс может выглядеть примерно так:

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

Другие советы

Обе новинки будут слиты в сеть.

Назначьте адрес объектов, созданных в куче, названный интеллектуальные указатели, чтобы они были удалены внутри деструктора интеллектуальных указателей, который вызывается при возникновении исключения - (РАИИ).

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

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

При обычном выделении памяти с использованием (не размещения) new память, выделенная оператором new, освобождается автоматически, если конструктор выдает исключение.Что касается того, зачем беспокоиться об освобождении отдельных членов (в ответ на комментарии к ответу Майка Б), автоматическое освобождение применяется только тогда, когда исключение создается в конструкторе вновь выделяемого объекта, а не в других случаях.Кроме того, освобождается память, выделенная для членов объекта, а не любая память, которую вы могли бы выделить, скажем, внутри конструктора.то естьЭто освободит память для переменных-членов а, пинта, объектныйкласс, и psomeclass, но не память, выделенная из новый какой-то класс() и новый интервал().

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

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

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

Удачи, Цви.

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

Да, этот код приведет к утечке памяти.Блоки памяти, выделенные с использованием «нового», не освобождаются при возникновении исключения.Это часть мотивации РАИИ.

Чтобы избежать утечки памяти, попробуйте что-то вроде этого:

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

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

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

Вызовет утечку памяти, потому что вам нужно сделать:

delete pint;
delete psomeclass;

В блокеfinally, чтобы избежать их утечки.

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

base temp = base();

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

base temp;

Добавлять «= base()» нет необходимости.

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

РВенди

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top