Подтвердить что ты не робот

Кто удаляет память, выделенную во время "новой" операции, которая имеет исключение в конструкторе?

Я действительно не могу поверить, что не мог найти ясного ответа на этот вопрос...

Как вы освобождаете память, выделенную после того, как конструктор класса С++ генерирует исключение, в случае, когда он инициализируется с помощью оператора new. Например:.

class Blah
{
public:
  Blah()
  {
    throw "oops";
  }
};

void main()
{
  Blah* b = NULL;
  try
  {
    b = new Blah();
  }
  catch (...)
  {
    // What now?
  }
}

Когда я это пробовал, b имеет значение NULL в блоке catch (что имеет смысл).

При отладке я заметил, что conrol входит в процедуру распределения памяти перед тем, как он попадает в конструктор.

Это на веб-сайте MSDN похоже, подтверждает это:

Когда новый используется для выделения памяти для объекта класса С++, объект конструктор вызывается после памяти выделяется.

Итак, имея в виду, что локальная переменная b никогда не назначается (т.е. NULL в блоке catch), как вы удаляете выделенную память?

Было бы неплохо получить кросс-платформенный ответ на это. то есть, что говорит спецификация С++?

CLARIFICATION: Я не говорю о том, что класс выделил память в c'tor, а затем выбрасывает. Я ценю, что в этих случаях д'ор не будет называться. Я говорю о памяти, используемой для выделения объекта (Blah в моем случае).

4b9b3361

Ответ 1

Здесь вы должны обратиться к аналогичным вопросам здесь и здесь. В принципе, если конструктор создает исключение, вы в безопасности, что память самого объекта снова освобождается. Хотя, если во время конструктора была заявлена ​​другая память, вы сами можете освободить ее, прежде чем покинуть конструктор с исключением.

По вашему вопросу ВОЗ удаляет память, а ответ - это код, стоящий за новым оператором (который генерируется компилятором). Если он распознает исключение, оставляющее конструктор, он должен вызывать все деструкторы членов классов (так как они уже были успешно созданы до вызова кода конструктора) и освободить их память (можно сделать рекурсивно вместе с деструктором-вызовом, скорее всего, путем вызова их надлежащего удаления), а также освободить память, выделенную для этого класса. Затем он должен перебросить зацепившееся исключение из конструктора в вызывающего нового. Конечно, может быть больше работы, которая должна быть выполнена, но я не могу вытащить все детали из моей головы, потому что они соответствуют каждой реализации компилятора.

Ответ 2

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

Затем, если объект был выделен с помощью new, вызывается соответствующая функция освобождения (operator delete) с теми же дополнительными аргументами, которые были переданы в operator new. Например, new (std::nothrow) SomethingThatThrows() будет выделять память с помощью operator new (size_of_ob, nothrow), попытаться построить SomethingThatThrows, уничтожить все элементы, которые были успешно построены, а затем вызвать operator delete (ptr_to_obj, nothrow) при распространении исключения - это не приведет к утечке памяти.

То, что вам нужно быть осторожным, - это выделение нескольких объектов подряд - если один из более поздних бросков, предыдущие не будут автоматически освобождены. Лучше всего это с помощью умных указателей, поскольку в качестве локальных объектов их деструкторы будут вызваны во время разворачивания стека, а их деструкторы будут правильно освобождать память.

Ответ 3

Если конструктор выбрасывает выделенную для объекта память автоматически возвращается в систему.

Обратите внимание, что деструктор класса, который был выброшен, не будет вызываться.
Но деструктор любого базового класса (где завершился базовый конструктор) также будет вызываться.

Примечание:
Как большинство других людей отметили, что членам может понадобиться очистка.

Члены, которые были полностью инициализированы, будут иметь свои деструкторы, но если у вас есть какие-либо члены указателя RAW, которыми вы владеете (т.е. удалить в деструкторе), вам придется немного очистить их до того, как вы сделаете бросок (по другой причине нет использовать собственные RAW-указатели в вашем классе).

#include <iostream>

class Base
{
    public:
        Base()  {std::cout << "Create  Base\n";}
        ~Base() {std::cout << "Destroy Base\n";}
};

class Deriv: public Base
{
    public:
        Deriv(int x)    {std::cout << "Create  Deriv\n";if (x > 0) throw int(x);}
        ~Deriv()        {std::cout << "Destroy Deriv\n";}
};

int main()
{
    try
    {
        {
            Deriv       d0(0);  // All constructors/Destructors called.
        }
        {
            Deriv       d1(1);  // Base constructor and destructor called.
                                // Derived constructor called (not destructor)
        }
    }
    catch(...)
    {
        throw;
        // Also note here.
        // If an exception escapes main it is implementation defined
        // whether the stack is unwound. By catching in main() you force
        // the stack to unwind to this point. If you can't handle re-throw
        // so the system exception handling can provide the appropriate
        // error handling (such as user messages).
    }
}

Ответ 4

Из С++ 2003 Standard 5.3.4/17 - Новое:

Если какая-либо часть инициализации объекта, описанная выше, завершается сбросом исключения и может быть найдена подходящая функция освобождения, функция освобождения вызывается для освобождения памяти, в которой объект строится, после чего исключение продолжает распространяться в контексте нового выражения. Если не удается найти однозначную функцию дезадаптации, распространение этого исключения не приведет к освобождению памяти объектов. [Примечание: Это удобно, если вызываемая функция выделения не выделяет память; в противном случае это может привести к утечке памяти. ]

Таким образом, может быть или не быть утечка - это зависит от того, можно ли найти соответствующий деаллокатор (что обычно происходит, если оператор new/delete не был переопределен). В случае, когда имеется подходящий дезактиватор, компилятор отвечает за проводку в вызове, если конструктор выбрасывает.

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

Ответ 5

Долгое и короткое из них состоит в том, что если вы не сделали никаких других объектов в объекте (как в вашем примере), тогда выделенная память будет автоматически удалена. Однако любые новые операторы (или что-либо еще, что напрямую управляет памятью) необходимо обрабатывать в инструкции catch в конструкторе. В противном случае объект удаляется без удаления последующих распределений, и вы, мой друг, испытываете утечку.

Ответ 6

Цитата из С++ FAQ (parashift.com):

[17.4] Как обращаться с ресурсами, если мои конструкторы могут исключения?

Каждый элемент данных внутри вашего объекта должен очистить свой беспорядок.

Если конструктор генерирует исключение, деструктор объекта не запустить. Если ваш объект уже сделал что-то, что нужно отменить (например, выделение некоторой памяти, открытие файла или блокировка семафор), этот "материал, который нужно отменить" нужно помнить через элемент данных внутри объекта.

Например, вместо выделения памяти в необработанные данные Fred*член, помещает выделенную память в объект-член "умный указатель", и деструктор этого умного указателя будет delete Fredобъект, когда умный указатель умирает. Шаблон std::auto_ptr является Например, например, "умный указатель". Вы также можете написать свой собственный ссылающийся на интеллектуальный указатель. Вы также можете использовать интеллектуальные указатели "указывать" на записи диска или объекты на других машинах.

Кстати, если вы думаете, что ваш класс Fred будет выделен в умный указатель, будьте добры к своим пользователям и создайте typedefв классе Fred:

 #include <memory>

 class Fred {
 public:
   typedef std::auto_ptr<Fred> Ptr;
   ...
 };

Это typedef упрощает синтаксис всего кода, который использует ваш объекты: ваши пользователи могут сказать Fred::Ptr вместо std::auto_ptr<Fred>:

 #include "Fred.h"

 void f(std::auto_ptr<Fred> p);  // explicit but verbose
 void f(Fred::Ptr           p);  // simpler

 void g()
 {
   std::auto_ptr<Fred> p1( new Fred() );  // explicit but verbose
   Fred::Ptr           p2( new Fred() );  // simpler
   ...
 }

Ответ 7

Описанная проблема так же стара, как и дорога в Рим, чтобы использовать голландскую поговорку. Я решил проблему, и выделение памяти для объекта, который может вызвать исключение, выглядит следующим образом:

try
{
    std::string *l_string =
        (_heap_cleanup_tpl<std::string>(&l_string),
        new std::string(0xf0000000, ' '));
    delete l_string;
}
catch(std::exception &)
{
}

Перед фактическим вызовом new -оператора создается безымянный (временный) объект, который получает адрес выделенной памяти через пользовательский новый оператор (см. остальную часть этого ответа). В случае нормального выполнения программы временный объект передает результат нового оператора (вновь созданного и полностью сконструированного объекта, в нашем случае очень очень длинную строку) в переменную l_string. В случае исключения значение не передается, но деструктор временного объекта удаляет память (без вызова деструктора основного объекта).

Это немного нечеткий способ решения проблемы, но она работает. Проблемы могут возникнуть из-за того, что для этого решения требуется назначенный пользователем новый оператор и пользовательский оператор удаления. Пользовательские операторы new/delete должны будут вызывать стандартную библиотечную реализацию С++ для новых/delete-операторов, но я оставил это для краткости и вместо этого использовал malloc() и free().

Это не окончательный ответ, но я думаю, что стоит это сделать.

PS: В приведенном ниже коде была "недокументированная" функция, поэтому я сделал улучшение.

Код для временного объекта выглядит следующим образом:

class _heap_cleanup_helper
{
    public:
    _heap_cleanup_helper(void **p_heap_block) :
        m_heap_block(p_heap_block),
        m_previous(m_last),
        m_guard_block(NULL)
    {
        *m_heap_block = NULL;
        m_last = this;
    }
    ~_heap_cleanup_helper()
    {
        if (*m_heap_block == NULL) operator delete(m_guard_block);
        m_last = m_previous;
    }
    void **m_heap_block, *m_guard_block;
    _heap_cleanup_helper *m_previous;
    static _heap_cleanup_helper *m_last;
};

_heap_cleanup_helper *_heap_cleanup_helper::m_last;

template <typename p_alloc_type>
class _heap_cleanup_tpl : public _heap_cleanup_helper
{
    public:
    _heap_cleanup_tpl(p_alloc_type **p_heap_block) :
        _heap_cleanup_helper((void **)p_heap_block)
    {
    }
};

Пользовательский новый оператор выглядит следующим образом:

void *operator new (size_t p_cbytes)
{
    void *l_retval = malloc(p_cbytes);

    if (
        l_retval != NULL &&
        *_heap_cleanup_helper::m_last->m_heap_block == NULL &&
        _heap_cleanup_helper::m_last->m_guard_block == NULL
    )
    {
        _heap_cleanup_helper::m_last->m_guard_block = l_retval;
    }
    if (p_cbytes != 0 && l_retval == NULL) throw std::bad_alloc();

    return l_retval;
}

void operator delete(void *p_buffer)
{
    if (p_buffer != NULL) free(p_buffer);
}

Ответ 8

Я думаю, что для конструктора возникает нечто странное, чтобы создать исключение. Не могли бы вы получить возвращаемое значение и протестировать его в основном?

class Blah
{
   public:

   Blah()
       {
           if Error
           {
              this.Error = "oops";
           }
        }
};

void main()
{
Blah* b = NULL;

b = new Blah();

if (b.Error == "oops")
{
   delete (b);
   b = NULL;
}