C ++ RAII не работает?

Я только начинаю работать с RAII на C ++ и настраиваю небольшой тестовый пример. Либо мой код сильно запутан, либо RAII не работает! (Я думаю, что это первое).

Если я бегу:

#include <exception>
#include <iostream>
class A {
public:
    A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; }
    ~A() { std::cout << "A " << i_ << " destructed" << std::endl; }
private:
    int i_;
};

int main(void) {
    A a1(1);
    A a2(2);
    throw std::exception();
    return 0;
}

за исключением закомментированного я получаю:

A 1 constructed
A 2 constructed
A 2 destructed
A 1 destructed

как и ожидалось, но за исключением я получаю:

A 1 constructed
A 2 constructed
terminate called after throwing an instance of 'std::exception'
  what():  std::exception
Aborted

поэтому мои объекты не разрушаются, даже если они выходят за пределы области видимости. Разве это не вся основа для RAII.

Указатели и исправления приветствуются!


person John    schedule 09.07.2009    source источник
comment
Вы тоже нашли ошибку в C ++! знак равно   -  person Eric    schedule 09.07.2009
comment
Если исключение ускользает из main (), это определяется реализацией, когда стек разматывается. Таким образом, добавьте блок try {} catch (...) {} в main, и все будет в порядке.   -  person Martin York    schedule 09.07.2009
comment
Разве вы не имеете в виду RIAA?   -  person Mechanical snail    schedule 29.09.2011


Ответы (10)


У вас нет обработчика для вашего исключения. Когда это происходит, стандарт говорит, что вызывается std :: terminate, который, в свою очередь, вызывает abort. См. Раздел 14.7 в языке программирования C ++, 3-е издание.

person Aaron Saarela    schedule 09.07.2009
comment
На самом деле вы можете установить свою собственную функцию завершения, но я не знаю, действительно ли это полезная возможность. - person David Thornley; 09.07.2009
comment
Ну, его можно использовать, скажем, для регистрации завершения в конкретном файле или для отправки кому-то электронного письма. - person Raphaël Saint-Pierre; 09.07.2009
comment
Как указал RaphaelPS, написание вашей собственной функции завершения полезно, если вы хотите регистрировать прекращение или очистку глобальных ресурсов. Однако вы не можете восстановиться после завершения. Обработчик завершения не принимает аргументов, не имеет возвращаемого значения и должен выйти из приложения. - person Aaron Saarela; 10.07.2009

Проблема в том, что main имеет особый статус. Когда оттуда генерируется исключение, стек не может быть осмысленно развернут, вместо этого приложение просто вызывает std:terminate.

И тогда становится понятным, почему переменные не выходят за рамки. Мы фактически не вышли из области, в которой они были объявлены. То, что происходит, можно считать эквивалентным этому:

int main(void) {
  A a1(1);
  A a2(2);
  std::terminate();
}

(Я считаю, что это зависит от реализации, вызываются ли деструкторы в этом случае, поэтому на некоторых платформах он будет работать так, как вы ожидали)

person jalf    schedule 09.07.2009
comment
Почему определена эта реализация? Разве не было бы больше смысла, если бы конструкторы вызывались всегда? C ++ иногда может быть просто глупым ... - person Zifre; 09.07.2009
comment
Не глупо - практично. Принуждение к постоянному вызову деструкторов (не конструкторов) ограничит способы реализации EH и развертывания стека. - person ; 09.07.2009
comment
Это может быть глупо, но это также прагматично. Спецификация C ++ пытается избежать ограничения разработчиков. В идеале они хотят, чтобы была возможность создать соответствующую стандартам реализацию C ++ на любом ЦП и любой ОС. А поскольку функция main в некотором смысле обозначает границу между ОС и C ++, они не хотят слишком много предполагать по этому поводу. Вы правы, в реальном мире было бы неплохо, если бы эту конкретную деталь не оставили на усмотрение реализации. Мы знаем это сейчас, но было ли это так очевидно в 1998 году, когда язык стандартизировался? Они не хотели загонять себя в угол - person jalf; 09.07.2009
comment
@jalf: может пора вернуться к вопросу? - person just somebody; 05.02.2010
comment
@just: Для чего? Где-то еще могут быть реализации, которые возможны только с этой дополнительной свободой. Более того, это вызовет путаницу (реализует ли мой компилятор новое строгое поведение или оно еще не обновлено?), И это не имеет большого значения, не так ли? Когда вы впервые столкнетесь с этой проблемой, это может быть удивительно, но ее довольно легко обнаружить и исправить, когда она становится проблемой в вашем коде. - person jalf; 05.02.2010
comment
@jalf: Прирост очевиден, но вы спрашиваете о чистом приросте, а это не так. В любом случае у меня нет сильного предчувствия по этому поводу, но я считаю, что ваши аргументы немного слабоваты. 1) у потенциальных неконформных реализаций было бы достаточно времени и места, чтобы ознакомить комитет с их ситуацией (и я уверен, что Страуструп не был бы счастлив оставить их в пыли, поскольку поддержка ограниченных систем - одна из его задач). хвастовство), 2) запутанные семантические изменения в C ++ имеют прецеденты, auto - очевидный пример. но у вас больше шансов получить ошибки времени компиляции, а не выполнения, с auto. - person just somebody; 05.02.2010
comment
Моя точка зрения на самом деле заключалась не в том, что несоответствующие реализации будут возражать, а в том, что обязательно будет переходный период, когда некоторые реализации исправят это поведение, а другие еще не исправили, вызывая путаницу, хотя бы временно. . Я не совсем понимаю вашу точку зрения на auto, поскольку он не меняет семантику существующего кода. - person jalf; 05.02.2010
comment
В конце концов, комитет не любит возвращаться к проблемам, если нет серьезных проблем с текущей спецификацией. Отчасти потому, что это может отрицательно повлиять на существующий код, а отчасти потому, что у них есть множество других проблем, которые более важны. - person jalf; 05.02.2010

У вас есть необработанное исключение в main, что означает завершение вызова. Попробуй это:

int main(void)
{
    try
    {
        A a1(1);
        A a2(2);
        throw std::exception();
        return 0;
    }
    catch(const std::exception & e)
    {
        return 1;
    }


}
person Edouard A.    schedule 09.07.2009
comment
Так что при использовании RAII я всегда должен заключать main в try / catch? - person John; 09.07.2009
comment
Я думаю, что main () другой. Попробуйте поместить все в функцию, которую вы вызываете из main (). - person Jason S; 09.07.2009
comment
Нет, только когда вы делаете что-то, что может вызвать исключение. - person Alex; 09.07.2009
comment
... или, по крайней мере, убедитесь, что вы поместили try / catch в самый внешний блок в main (). - person Jason S; 09.07.2009
comment
Не всегда, только в main (). Попробуйте переместить код в отдельную функцию и оставить обработчик исключений в main. - person Nemanja Trifunovic; 09.07.2009
comment
Вам нужно будет попробовать {} catch () где-нибудь, иначе вы просто завершите работу, когда дойдете до самой внешней области. - person Peter Kovacs; 09.07.2009

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

пытаться

int main()
{
    try
    {
        doWork(); // Do you experiment here. 
    }
    catch(...)
    {   /*
         * By catching here you force the stack to unwind correctly.
         */
        throw;  // re-throw so exceptions pass to the OS for debugging.
    }
}
person Martin York    schedule 09.07.2009

Как указывали другие, у вас есть неперехваченное исключение, которое вызывает terminate (). Это определяется реализацией (см. Стандарт, 15.3 параграф 9 и 15.5.1 параграф 2), вызываются ли деструкторы в этом случае, и определение в вашей реализации, очевидно, «Нет, они не будут». (Если terminate () вызывается по любой другой причине, кроме выброса исключения, у которого нет обработчика, деструкторы не будут вызываться.)

person David Thornley    schedule 09.07.2009

Ваши объекты A не уничтожаются из-за вызова std :: terminate.

std :: terminate вызывается при утечке необработанного исключения из main. Если вы заключите свой код в команду try / catch (даже если уловка просто перерейзит), вы увидите ожидаемое поведение.

person Michael van der Westhuizen    schedule 09.07.2009

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

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

      1 #include <exception>
      2 #include <iostream>
      3
      4 void test();
      5
      6 class A {
      7     public:
      8         A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; }
      9         ~A() { std::cout << "A " << i_ << " destructed" << std::endl; }
     10     private:    int i_;
     11 };
     12
     13
     14 int main(void) {
     15     test();
     16     return 0;
     17 }
     18
     19 void test(){
     20             A a1(1);
     21             A a2(2);
     22            throw std::exception();
     23 }

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

person Alex    schedule 09.07.2009
comment
Даже если вы переместите код в другую функцию, если у вас нет команды try / catch, он переместится в главную и вызовет ту же проблему. Так что main совсем не особенный. - person Alex; 09.07.2009
comment
Вы ошибаетесь насчет неопределенного поведения. Поведение исключения без обработчика определяется в Стандарте, за исключением того, что независимо от того, разматывается стек или нет, определяется реализацией. - person David Thornley; 09.07.2009
comment
Изменен ответ, чтобы отразить это. - person Alex; 09.07.2009

Другие предлагали поместить в main() команду try / catch, чтобы справиться с этим, что отлично работает. По какой-то причине я считаю, что редко используемый «function-try-block» выглядит лучше, что меня удивляет (я думал, что это будет выглядеть слишком странно). Но я не думаю, что в этом есть реальное преимущество:

int main(void) 
try
{
    A a1(1);
    A a2(2);
    throw std::exception();
    return 0;
}
catch (...) 
{
    throw;
}

Пара недостатков заключается в том, что, поскольку он редко используется, многие разработчики зацикливаются, когда видят его, и VC6 подавляется этим, если это необходимо.

person Michael Burr    schedule 10.07.2009

Поскольку исключение не обрабатывается к тому моменту, когда оно достигает main (), оно приводит к вызову std :: terminate (), по сути, у вас есть эквивалент

int main(void) {
  A a1(1);
  A a2(2);
  exit(1);
}

НЕ гарантируется вызов деструкторов в случаях, когда программа завершается до того, как они выйдут за пределы области видимости. Для другой дыры в RAII рассмотрите:

int main(void) {
  A *a1 = new A(1);
}
person jkerian    schedule 09.07.2009
comment
Второй пример - это не дыра в RAII; это пример неиспользования RAII. - person Michael Burr; 10.07.2009

Следующий код работает.

#include <exception> 
#include <iostream> 

class A { 
public: 
    A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; } 
    ~A() { std::cout << "A " << i_ << " destructed" << std::endl; } 
private: 
    int i_; 
}; 

void test() {
    A a1(1); 
    A a2(2); 
    throw std::exception(); 
} 

int main(void) { 
 try {
  test();
 } catch(...) {
 }
    return 0; 
} 
person ggg    schedule 05.02.2010