Matvey Cherevko

Конструкторы и исключения (24 Jun 13)

Очень многое написано про исключения в конструкторах, кто-то за, кто-то против, как правильно готовить и как делать не надо. Всё это есть у Маерса, в куче форумов и т.д. Я же напишу про известную, но мало затрагиваемую часть сего действа.

Цитаты из стандарта:

“An object that is partially constructed will have destructors executed only for its fully constructed sub-objects.” (15.2.2)

“The fully constructed base classes and members of an object shall be destroyed before entering the handler of a function-try-block of a constructor or destructor for that block.” (15.3.11)

То есть деструктор вызовется у всех полностью сконструированных подобъектов и базовых классов объектов, которые полностью сконструировались. Поясню на примере.

struct Type {
	Type() {
		::printf("Type::Type()\n");
		throw 1;
	}
	~Type() {
		::printf("Type::~Type()\n");
	}
};
int main() {
	try {
		Type t;
	} catch(...) {
		::printf("catch(...)\n");
	}
	return 0;
}

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

Type::Type()
catch(...)

То есть вызвался конструктор Type, полетело исключение 1, перехватилось в catch(...), написали о перехвате, завершилось приложение. Теперь добавляем интересности в пример.

struct Base {
	Base() {
		::printf("Base::Base()\n");
	}
	~Base() {
		::printf("Base::~Base()\n");
	}
};
struct Derived : Base {
	Derived() {
		::printf("Derived::Derived()\n");
		throw 1;
	}
	~Derived() {
		::printf("Derived::~Derived()\n");
	}
};
int main() {
	try {
		Derived t;
	} catch(...) {
		::printf("catch(...)\n");
	}
	return 0;
}

Ну и вывод приложения:

Base::Base()
Derived::Derived()
Base::~Base()
catch(...)

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

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

P.S. Эту особенность можно использовать, например, в STL-like контейнерах, что и делают разработчики многих из её реализаций, а именно, выносят все аллокации, например, вектора в базовый класс, для того что бы не оборачивать конструкторы в try {} catch {}, а деаллоцируют память из его деструктора, что повышает и читаемость и производительность.