2012-01-30 2 views
14

Я очень часто использовал списки инициализации в своих программах на C++, но не знал, что вы можете выделить в них память.Есть ли проблемы с распределением памяти в списках инициализации конструктора?

Так что вы можете сделать что-то (как надуманный пример), как это:

class Test 
{ 
private: 
    int* i; 
    int* j; 
    int count; 
    int* k; 

public: 
    Test(void) : i(new int), j(new int[10]), count(10), k(new int[count]) 
    { 
    } 

    ~Test(void) 
    { 
     delete i; 
     delete [] j; 
     delete [] k; 
    } 
}; 

Существуют ли какие-либо проблемы в делать выделения памяти таким образом? Что касается порядка инициализации здесь, безопасно ли инициализировать параметр одним инициализированным в том же списке? то есть, когда я выделяю count, прежде чем использовать его, можно ли его использовать или есть какой-то специальный порядок инициализации, который я мог бы испортить?

+2

Обратите внимание, что порядок инициализации не определяется инициализатором ctor, а порядком декларации переменных. Также обратите внимание, что правильный ответ, возможно, зависит от того, хотите ли вы, чтобы ваш код был безопасным для исключений при наличии нового метания или нет. – PlasmaHH

ответ

18

Это не исключение. Если new для j выбрасывает исключение, деструктор для Test не вызывается, поэтому память для i не освобождается.

Вызывается деструктор i, если инициализатор для j выбрасывает, это просто, что необработанный указатель не имеет деструктора. Таким образом, вы можете сделать его безопасным для исключения, заменив i на подходящий умный указатель. В этом случае unique_ptr<int> для i и unique_ptr<int[]> для j будет делать.

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

+0

В частности, это проблема, если класс вызывает 'new' более одного раза в списке инициализаторов. :) – jalf

+0

Я знаю, что вы можете добавить обработчики исключений в список инициализации, если вы это сделаете, чтобы решить эту проблему или сделать другие? – Firedragon

+0

Это не большая проблема - большинство людей делают несколько «новых» в конструкторе и не заботятся об их обертывании в блок try - в обычном случае (не серверное программное обеспечение и т. Д.) Запуск -out-of-memory-case - OMG-мы снимаем ситуацию с лодкой. –

4

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

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

2

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

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

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

+0

«Это, как правило, не требуется» - лично я это делаю в любом случае, так как решение о том, нужно ли это или нет, не является точной наукой. В любом случае это делает код более простым, без необходимости деструктора. –

+0

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

+0

@Steve, жизнь была бы красивее, если бы все это сделали (и я сам не использую выделение необработанных указателей), но я просто указал, что для большинства людей это не проблема. –

1

Нет проблем с вызовом new из списка инициализаторов.

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

Как полагаться на порядок инициализации, это совершенно безопасно. Члены инициализируются в том порядке, в котором они перечислены в объявлении класса. Таким образом, вы можете использовать значение элементов, инициализированных раньше, для инициализации «более поздних» членов.

Просто имейте в виду, что это их порядок объявления внутри класса, и не их порядок в списке инициализации, который определяет их порядок инициализации. :)

+0

Я правильно говорю, что это сработает, хотя это выглядит странно, и я считаю, что многие стандарты говорят, что вы должны делать их в порядке, как в классе? 'Test (void): k (new int [count]), count (10)' – Firedragon

+1

@Firedragon: Да, если 'count' и' k' остаются в правильном порядке относительно друг друга в определении класса. – Mankarse

+1

@Firedragon: да, это все равно будет работать. GCC предупреждал об этом, хотя, так как это сбивает с толку читать. –

1

Предположим, что у вас есть:

class Foo 
{ 
public: 
    T* p1; 
    T* p2; 

    Foo() 
    : p1(new T), 
     p2(new T) 
    { 
    } 
}; 

Если инициализация p2 не удается (либо потому, что new выбрасывает аут исключения памяти или из-за T конструктор не может), то p1 будет утечка. Для борьбы с этим, C++ позволяет использовать try/catch в списках инициализации, но обычно это довольно грубо.

3

Вы назначаете память в свой список-инициализатор; это совершенно нормально, но затем вы назначаете указатели на эту память на исходные указатели.

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

Гораздо лучший способ писать Test будет: ответ

class Test 
{ 
private: 
    //Assuming you actually want dynamic memory allocation: 
    std::unique_ptr<int> i; 
    std::unique_ptr<int[]> j; 
    int count; 
    std::unique_ptr<int[]> k; 

public: 
    Test(void) : i(new int), j(new int[10]), count(10), k(new int[count]) 
    { 
    } 
}; 
1

Steve Джессоп в представлены подводные камни.

О порядке, который я думаю, что ваш вопрос, адресованный:

12.6.2/4

Инициализация должна происходить в следующем порядке:

[...]

  • Затем нестатические элементы данных должны быть инициализированы в том порядке, в котором они были объявлены в определении класса (опять же независимо от порядка из mem-инициализаторов).

Поскольку в вашем классе, count объявляется перед тем k, ты в порядке.

Смежные вопросы