Skip to main content

А возможно и не const...

Прочитав недавно в блоге Алёны С++ статью Возможно, самый важный const, губоко задумался нельзя ли все-таки как нибудь обойти запрет на следующее

string&s = string("abc");
int&i = 1;

И совершенно неожиданно вспомнил, что когда-то давным-давно на далеком-далеком форуме (чет меня опять не туда понесло ;-)) прочитал о том, как тимлид учил зеленых программеров: "тыкаем в любое место throw 1; если программа летит - бьем дубиной по голове"

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

try {
throw 1;
}
catch(int&i){
//можем менять i
}
catch(...){}


Проверяем

try {
try {
throw 1;
}
catch(int&i){
cout<<"int& "<<i<<endl;
i=2;
throw;
}
catch(...){}
}
catch(int&i){
cout<<"int& "<<i<<endl;
}
catch(...){}


Факт повторной генерации исключения отмечается отсутствием операнда у throw. Причем повторно сгенерированное исключение является исходным исключением (копией или нет - думаю зависит от реализации, по крайней мере в Стандарте ничего не нашел по этому поводу. Может плохо искал? Пните меня в комментах, если найдете). Хотя по идее оно должно копироваться, так как это все же генерация, хоть и повторная. Однако в реализации gcc version 4.1.2 20061115 (prerelease) (Debian 4.1.1-21), которой я на данный момент пользуюсь, в момент повторной генерации эксепшн не копируется.


#include <iostream>
#include <cstdlib>

//класс исключения
class ex {
public:
ex(const ex&){cout<<"Copy ctor"<<endl;};
ex(){cout<<"ctor"<<endl;};
~ex(){cout<<"dtor"<<endl;}
};
//
int main(int argc, char *argv[])
{
try {
try {
throw ex();
}
catch(ex&){cout<<"Rethrow"<<endl;throw;}
catch(...){throw;}
}
catch(ex&){}
catch(...){}
return EXIT_SUCCESS;
}

Причем, что интересно, на отсутствие copy constructor ругается, если его private или explicit сделать, а в процессе распространения исключения и при генерации его не использует.

Update
В результате пинков открыл таки 15.1 пункт Стандарта и почитал его. И как правильно заметили в комментариях, время жизни ссылки все равно ограничено обработчиком и не превышает время жизни временной переменной, а время жизни временной переменной, в свою очередь, определяется другим механизмом, а не наличием ссылок на нее.

Update2
15.1.6
A throw-expression with no operand rethrows the exception beeing handled. The exception is reactivated with existing temporary; no new temporary exception object is created.

Так что во время повторной генерации исключения копирования не происходит.


Comments

  1. Сформулирую по пунктам.
    1. При повторной генерации исключения нового временного объекта создаваться не должно, т.е. от обработчика к обработчику будет путешествовать один и тот же временный объект.
    15.1.4 [...] The temporary persists as long as there is a handler being executed for that exception. In particular, if a handler exits by executing a throw; statement, that passes control to another handler for the same exception, so the temporary remains. When the last handler being executed for the exception exits by any means other than throw; the temporary object is destroyed and the implementation may deallocate the memory for the temporary object; [...]. The destruction occurs immediately after the destruction of the object declared in the exception-declaration in the handler.

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

    3. По поводу копирующего конструктора. Использование временного объекта может быть исключено. Только вот из пункта 15.1.5 мне что-то не понятно, в каком случае. К слову, должен быть ещё и доступный конструктор (15.1.5)

    ReplyDelete
  2. Спасибо за пинок ;-). Таки плохо искал.
    Отвечу по пунктам цитатами из Б. Страуструп "Язык программирования С++"
    1. "В принципе, исключение в момент его генерации копируется, поэтому обработчик имеет копию исходного исключения. В действительности, исключение может скопироваться несколько раз до того, как оно будет перехвачено." Т.е. - зависимо от реализации. Конкретно в моем случае gcc требует копирующий к-р, но не использует его.
    По поводу the same, то тут Страуструп поясняет
    class A{};
    class B: public A{};
    try{
    try {
    throw B();
    }
    catch(A&){
    /*повторно генерируется B а не просто его часть, которая была доступна как A*/
    throw;
    }
    }
    catch(B&){
    }
    Какой именно temporary remains непонятно, скорее всего в большинстве реализаций - исходный.

    2. Да, просто в данном случае время жизни временной переменной определяется другими правилами и не ограничено throw ex(); По идее очевидно должна создаваться временная переменная, затем она должна копироваться и копия отправляется в путешествие вверх по стеку, а исходная уничтожается. Это если исходить из вышесказанного.

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

    В общем подытоживая, можно сказать, что разработчикам компиляторов все же предоставлена некоторая свобода действий. А обычных программеров это в большинстве своем не должно трогать.

    ReplyDelete
  3. Да и не пинок совсем это был. Мне и самому было интересно ;-)

    По поводу того, какой temporary remains.
    В момент выполнения throw B(); создаётся временный объект in unspesified way (15.1.4), ссылка на который передаётся в конечном счёте в catch( A& ). Если в обработчике выполняется throw; (что в примере и происходит), то не создастся новый объект-копия исходного, а тот же самый временный объект продолжает путешествие дальше к следующему обработчику (15.1.4). Временный объект будет удалён, когда его наконец кто-нибудь обработает (тоже 15.1.4)

    А по поводу пункта 3 главное знать, что копирующий конструктор может и не быть вызван. Всё остальное действительно лежит в зоне ответственности разработчиков компиляторов.

    ReplyDelete
  4. In particular, if a handler exits by executing a throw; statement, that passes control to another handler for the same exception, so the temporary remains [15.1.4]

    В частности, если обработчик завершается выполнением инструкции throw;, которая передает управление другому обработчику того-же самого исключения (я думаю, что тут имеется ввиду исключение как класс), то временная переменная сохраняется.

    Здесь явно не указано, что эта временная переменная должна быть тем же самым объектом, что и до выполнения throw;. Просто и далее, пока не будет обработано исключение, будет существовать некая область памяти, занятая под экземпляр исключения.

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

    ReplyDelete

Post a Comment

СООБЩЕНИЕ СПАМЕРАМ: прежде чем пытаться оставить ссылку на свой ресурс в комментарии, прошу обратить внимание на тег nofollow, которым они помечены и зря не терять ни свое ни мое время. А будете упорствовать еще и noindex поставлю