Может кто-нибудь объяснить, что именно происходит, если в процессе выделения массива объектов в куче возникает исключение? - PullRequest
3 голосов
/ 17 ноября 2011

Я определил класс foo следующим образом:

class foo {
private:
   static int objcnt;
public:
   foo() {
       if(objcnt==8)
           throw outOfMemory("No more space!");
       else
          objcnt++;
   }

   class outOfMemory {
   public:
       outOfMemory(char* msg) { cout << msg << endl;}
   };

   ~foo() { cout << "Deleting foo." << endl; objcnt--;}
};
int foo::objcnt = 0;

И вот основная функция:

int main() {
    try {
            foo* p = new foo[3];
            cout << "p in try " << p << endl;
            foo* q = new foo[7];
        }catch(foo::outOfMemory& o) {
           cout << "Out-of-memory Exception Caught." << endl;
        }
}

Очевидно, что строка "foo * q = new foo [7];»успешно создает только 5 объектов, а для 6-го объекта выдается исключение «Недостаточно памяти».Но оказывается, что есть только 5 вызовов деструкторов, и destrcutor не вызывается для массива из 3 объектов, хранящихся в позиции, на которую указывает точка p.Вот мне и интересно почему?Как получается, что программа вызывает деструктор только для этих 5 объектов?

Ответы [ 3 ]

6 голосов
/ 17 ноября 2011

«Атомные» функции размещения и построения C ++ являются правильными и безопасными для исключений: If new T;броски, ничего не протекает, и если new T[N] бросает куда-нибудь по пути, все, что уже было построено, уничтожается.Так что не о чем беспокоиться.

Теперь отступление:

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

Рассматривать new и new[] строго как строительные блоки библиотеки:Вы никогда не будете использовать их в высокоуровневом пользовательском коде (возможно, за исключением одного new в конструкторе) и только внутри библиотечных классов.

Для:

// BAD:
A * p = new A;
B * q = new B;  // Ouch -- *p may leak if this throws!

// Good:
std::unique_ptr<A> p(new A);
std::unique_ptr<B> q(new B); // who cares if this throws
std::unique_ptr<C[3]> r(new C[3]); // ditto

В качестве еще одного отступления: стандартные библиотечные контейнеры реализуют аналогичное поведение: если вы говорите resize(N) (растущий), и во время любых конструкций возникает исключение, тогда всех изуже построенные элементы разрушаются.То есть resize(N) либо увеличивает контейнер до указанного размера, либо не увеличивает его вообще. (Например, в GCC 4.6 см. Реализацию _M_fill_insert() в bits/vector.tcc для библиотечной версии построения диапазона с проверкой на исключение.)

6 голосов
/ 17 ноября 2011

Деструкторы вызываются только для полностью построенных объектов - это объекты, чьи конструкторы завершены нормально.Это происходит автоматически, только если выдается исключение во время выполнения new[].Таким образом, в вашем примере деструкторы будут запущены для пяти объектов, полностью построенных во время работы q = new foo[7].

Поскольку new[] для массива, который p указывает на успешное завершение, этот массив теперь обрабатывается вашим кодом исреда выполнения C ++ больше не заботится об этом - никакие деструкторы не будут запущены, если вы не выполните delete[] p.

0 голосов
/ 17 ноября 2011

Вы получаете ожидаемое поведение при объявлении массивов в куче:

int main()
{
    try
    {
        foo   p[3];
        cout << "p in try " << p << endl;
        foo   q[7];
    }
    catch(foo::outOfMemory& o)
    {
       cout << "Out-of-memory Exception Caught." << endl;
    }
}

В вашем коде только указатели были локальными автоматическими переменными.Указатели не имеют никакой связанной очистки, когда стек разматывается.Как уже отмечали другие, именно поэтому у вас, как правило, нет указателей RAW в коде C ++, они обычно заключаются в объект класса, который использует конструктор / деструктор для управления их продолжительностью жизни (умный указатель / контейнер).

Какпримечание стороныОбычно лучше использовать std :: vector, чем необработанные массивы (в C ++ 11 std :: array также полезен, если у вас есть массив фиксированного размера).Это связано с тем, что размер стека ограничен, и эти объекты помещают большую часть данных в кучу.Дополнительные методы, предоставляемые этими объектами класса, делают их намного приятнее для обработки в остальной части кода, и если вам абсолютно необходимо иметь указатель массива старого стиля для передачи функции C, их легко получить.

int main()
{
    try
    {
        std::vector<foo>     p(3);
        cout << "p in try " << p << endl;
        std::vector<foo>     q(7);

        // Now you can pass p/q to function much easier.

    }
    catch(foo::outOfMemory& o)
    {
       cout << "Out-of-memory Exception Caught." << endl;
    }
}
...