C++:在 throwing/catching 异常中,异常对象何时被析构?
C++: In throwing/catching exceptions, when are the exception objects destructed?
我看到这篇文章:Best practice: throw by value, catch by const reference 并对异常对象何时被销毁感到好奇。
这是我的异常结构,就像文章中的第二个例子(有一些std::cout)。
struct BASE_EX {
static int id;
BASE_EX() {
++id;
std::cout << "constructing BASE_EX " << id << std::endl;
}
virtual std::string const what() const { return "BASE_EX " + std::to_string(id); }
~BASE_EX() { std::cout << "destructing BASE_EX " << id << std::endl; }
};
struct DERIVED_EX : BASE_EX {
static int derived_id;
DERIVED_EX() {
++derived_id;
std::cout << "constructing DERIVED_EX " << derived_id << std::endl;
}
std::string const what() const { return "DERIVED_EX " + std::to_string(derived_id); }
~DERIVED_EX() { std::cout << "destructing DERIVED_EX " << derived_id << std::endl; }
};
int BASE_EX::id = 0;
int DERIVED_EX::derived_id = 0;
当运行这个main函数时,通过const&:
捕获
int main() {
try {
try {
throw DERIVED_EX();
} catch(BASE_EX const& ex) {
std::cout << "First catch block: " << ex.what() << std::endl;
throw ex;
}
} catch(BASE_EX const& ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
}
我得到了
constructing BASE_EX 1
constructing DERIVED_EX 1
First catch block: DERIVED_EX 1
destructing DERIVED_EX 1
destructing BASE_EX 1
Second catch block: BASE_EX 1
destructing BASE_EX 1
问题1:如果BASE_EX在第二次catch之前就被析构了,怎么捕获?
问题二:为什么破坏多于建设?
问题3:当我将两个catch都改为按值而不是const&时,为什么输出变成了
constructing BASE_EX 1
constructing DERIVED_EX 1
First catch block: BASE_EX 1
destructing BASE_EX 1
destructing DERIVED_EX 1
destructing BASE_EX 1
Second catch block: BASE_EX 1
destructing BASE_EX 1
destructing BASE_EX 1
任何关于 cpp try-catch 如何在引擎盖下工作的推荐读物都会很棒。谢谢。
了解它们何时被销毁的最好方法是假装 catch
类是一个函数:
catch(BASE_EX const& ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
临时调整一下,假装这是一个函数,ex
只是这个函数的一个参数:
void exception_handler(BASE_EX const& ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
只要这个伪函数参数被销毁,异常对象就会被销毁,如果异常处理程序像普通函数调用一样进入,在这里。
如果您要标记对象的构造函数,请标记 所有 ;)
struct BASE_EX {
static int count;
int id;
BASE_EX() : id(count++) {
std::cout << "constructing BASE_EX " << id << std::endl; // usually std::endl is unnecessary (it's just "\n" followed by std::flush), but since we're playing with crashes it's probably a good idea
}
BASE_EX(BASE_EX const &other) : id(count++) {
std::cout << "copying BASE_EX " << other.id << " as BASE_EX " << id << std::endl;
}
// implicit move constructor not declared
virtual std::string what() const { return "BASE_EX " + std::to_string(id); } // marking by-value return as const does absolutely nothing
~BASE_EX() { std::cout << "destructing BASE_EX " << id << std::endl; } // reminder that base class destructors should generally be virtual; not required in this case
};
int BASE_EX::count = 0;
struct DERIVED_EX : BASE_EX {
static int count;
int id;
DERIVED_EX() : BASE_EX(), id(count++) {
std::cout << "constructing DERIVED_EX " << id << std::endl;
}
DERIVED_EX(DERIVED_EX const &other) : BASE_EX(other), id(count++) {
std::cout << "copying DERIVED_EX " << other.id << " as DERIVED_EX " << id << std::endl;
}
// implicit move constructor not declared
std::string what() const override { return "DERIVED_EX " + std::to_string(id); }
~DERIVED_EX() { std::cout << "destructing DERIVED_EX " << id << std::endl; }
};
int DERIVED_EX::count = 0;
你得到
constructing BASE_EX 0
constructing DERIVED_EX 0
First catch block: DERIVED_EX 0
copying BASE_EX 0 as BASE_EX 1
destructing DERIVED_EX 0
destructing BASE_EX 0
Second catch block: BASE_EX 1
destructing BASE_EX 1
第一个throw
设置异常对象为DERIVED_EX 0
。内部 catch
获取对该异常对象的 BASE_EX 0
基础 class 子对象的引用。由于 what
是 virtual
,调用它会导致 DERIVED_EX
报告其类型。但是,当你再次throw ex
时,ex
只有静态类型BASE_EX
,所以新的异常对象被选择为BASE_EX
,它是通过复制只有第一个异常对象的BASE_EX
部分。当我们退出第一个 catch
时,第一个异常对象被销毁,并且外部 catch 接收到新的 BASE_EX
对象。因为它确实 是 一个 BASE_EX
,而不是 DERIVED_EX
,调用 what
反映了这一点。如果你使两个 catch
都按值,你会得到
constructing BASE_EX 0
constructing DERIVED_EX 0
copying BASE_EX 0 as BASE_EX 1
First catch block: BASE_EX 1
copying BASE_EX 1 as BASE_EX 2
destructing BASE_EX 1
destructing DERIVED_EX 0
destructing BASE_EX 0
copying BASE_EX 2 as BASE_EX 3
Second catch block: BASE_EX 3
destructing BASE_EX 3
destructing BASE_EX 2
当你catch
按值时,复制异常对象来初始化catch参数。在这样一个 catch
块的执行期间,有两个对象表示异常:实际的异常对象,它没有名字,以及它为 catch
块制作的副本,可以命名.第一个副本是第一个异常对象到第一个catch
的参数的副本。第二个副本是作为第二个异常对象的那个参数的副本。第三个是将该异常对象复制到第二个 catch
的参数中。当我们输入第一个 catch
时,异常的 DERIVED_EX
部分已被切掉。根据通常的作用域规则,catch
参数在每个 catch
结束时被销毁。只要相应的 catch
块退出,异常对象就会被销毁。
通过不按值接受异常并且不使用 throw <catch-parameter>
重新抛出异常,可以避免复制问题和切片问题。
int main() {
try {
try {
throw DERIVED_EX();
} catch(BASE_EX const &ex) {
std::cout << "First catch block: " << ex.what() << std::endl;
throw;
}
} catch(BASE_EX const &ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
}
给予
constructing BASE_EX 0
constructing DERIVED_EX 0
First catch block: DERIVED_EX 0
Second catch block: DERIVED_EX 0
destructing DERIVED_EX 0
destructing BASE_EX 0
异常对象没有在第一个 catch
结束时销毁,因为它以 throw
退出,这表明 same 异常对象用于匹配更多 catch
个子句。它不会被复制到一个新的异常对象中,也不会像 throw ex
那样被销毁。
有关规则的详细说明,请参阅cppreference。
我看到这篇文章:Best practice: throw by value, catch by const reference 并对异常对象何时被销毁感到好奇。
这是我的异常结构,就像文章中的第二个例子(有一些std::cout)。
struct BASE_EX {
static int id;
BASE_EX() {
++id;
std::cout << "constructing BASE_EX " << id << std::endl;
}
virtual std::string const what() const { return "BASE_EX " + std::to_string(id); }
~BASE_EX() { std::cout << "destructing BASE_EX " << id << std::endl; }
};
struct DERIVED_EX : BASE_EX {
static int derived_id;
DERIVED_EX() {
++derived_id;
std::cout << "constructing DERIVED_EX " << derived_id << std::endl;
}
std::string const what() const { return "DERIVED_EX " + std::to_string(derived_id); }
~DERIVED_EX() { std::cout << "destructing DERIVED_EX " << derived_id << std::endl; }
};
int BASE_EX::id = 0;
int DERIVED_EX::derived_id = 0;
当运行这个main函数时,通过const&:
捕获int main() {
try {
try {
throw DERIVED_EX();
} catch(BASE_EX const& ex) {
std::cout << "First catch block: " << ex.what() << std::endl;
throw ex;
}
} catch(BASE_EX const& ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
}
我得到了
constructing BASE_EX 1
constructing DERIVED_EX 1
First catch block: DERIVED_EX 1
destructing DERIVED_EX 1
destructing BASE_EX 1
Second catch block: BASE_EX 1
destructing BASE_EX 1
问题1:如果BASE_EX在第二次catch之前就被析构了,怎么捕获?
问题二:为什么破坏多于建设?
问题3:当我将两个catch都改为按值而不是const&时,为什么输出变成了
constructing BASE_EX 1
constructing DERIVED_EX 1
First catch block: BASE_EX 1
destructing BASE_EX 1
destructing DERIVED_EX 1
destructing BASE_EX 1
Second catch block: BASE_EX 1
destructing BASE_EX 1
destructing BASE_EX 1
任何关于 cpp try-catch 如何在引擎盖下工作的推荐读物都会很棒。谢谢。
了解它们何时被销毁的最好方法是假装 catch
类是一个函数:
catch(BASE_EX const& ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
临时调整一下,假装这是一个函数,ex
只是这个函数的一个参数:
void exception_handler(BASE_EX const& ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
只要这个伪函数参数被销毁,异常对象就会被销毁,如果异常处理程序像普通函数调用一样进入,在这里。
如果您要标记对象的构造函数,请标记 所有 ;)
struct BASE_EX {
static int count;
int id;
BASE_EX() : id(count++) {
std::cout << "constructing BASE_EX " << id << std::endl; // usually std::endl is unnecessary (it's just "\n" followed by std::flush), but since we're playing with crashes it's probably a good idea
}
BASE_EX(BASE_EX const &other) : id(count++) {
std::cout << "copying BASE_EX " << other.id << " as BASE_EX " << id << std::endl;
}
// implicit move constructor not declared
virtual std::string what() const { return "BASE_EX " + std::to_string(id); } // marking by-value return as const does absolutely nothing
~BASE_EX() { std::cout << "destructing BASE_EX " << id << std::endl; } // reminder that base class destructors should generally be virtual; not required in this case
};
int BASE_EX::count = 0;
struct DERIVED_EX : BASE_EX {
static int count;
int id;
DERIVED_EX() : BASE_EX(), id(count++) {
std::cout << "constructing DERIVED_EX " << id << std::endl;
}
DERIVED_EX(DERIVED_EX const &other) : BASE_EX(other), id(count++) {
std::cout << "copying DERIVED_EX " << other.id << " as DERIVED_EX " << id << std::endl;
}
// implicit move constructor not declared
std::string what() const override { return "DERIVED_EX " + std::to_string(id); }
~DERIVED_EX() { std::cout << "destructing DERIVED_EX " << id << std::endl; }
};
int DERIVED_EX::count = 0;
你得到
constructing BASE_EX 0
constructing DERIVED_EX 0
First catch block: DERIVED_EX 0
copying BASE_EX 0 as BASE_EX 1
destructing DERIVED_EX 0
destructing BASE_EX 0
Second catch block: BASE_EX 1
destructing BASE_EX 1
第一个throw
设置异常对象为DERIVED_EX 0
。内部 catch
获取对该异常对象的 BASE_EX 0
基础 class 子对象的引用。由于 what
是 virtual
,调用它会导致 DERIVED_EX
报告其类型。但是,当你再次throw ex
时,ex
只有静态类型BASE_EX
,所以新的异常对象被选择为BASE_EX
,它是通过复制只有第一个异常对象的BASE_EX
部分。当我们退出第一个 catch
时,第一个异常对象被销毁,并且外部 catch 接收到新的 BASE_EX
对象。因为它确实 是 一个 BASE_EX
,而不是 DERIVED_EX
,调用 what
反映了这一点。如果你使两个 catch
都按值,你会得到
constructing BASE_EX 0
constructing DERIVED_EX 0
copying BASE_EX 0 as BASE_EX 1
First catch block: BASE_EX 1
copying BASE_EX 1 as BASE_EX 2
destructing BASE_EX 1
destructing DERIVED_EX 0
destructing BASE_EX 0
copying BASE_EX 2 as BASE_EX 3
Second catch block: BASE_EX 3
destructing BASE_EX 3
destructing BASE_EX 2
当你catch
按值时,复制异常对象来初始化catch参数。在这样一个 catch
块的执行期间,有两个对象表示异常:实际的异常对象,它没有名字,以及它为 catch
块制作的副本,可以命名.第一个副本是第一个异常对象到第一个catch
的参数的副本。第二个副本是作为第二个异常对象的那个参数的副本。第三个是将该异常对象复制到第二个 catch
的参数中。当我们输入第一个 catch
时,异常的 DERIVED_EX
部分已被切掉。根据通常的作用域规则,catch
参数在每个 catch
结束时被销毁。只要相应的 catch
块退出,异常对象就会被销毁。
通过不按值接受异常并且不使用 throw <catch-parameter>
重新抛出异常,可以避免复制问题和切片问题。
int main() {
try {
try {
throw DERIVED_EX();
} catch(BASE_EX const &ex) {
std::cout << "First catch block: " << ex.what() << std::endl;
throw;
}
} catch(BASE_EX const &ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
}
给予
constructing BASE_EX 0
constructing DERIVED_EX 0
First catch block: DERIVED_EX 0
Second catch block: DERIVED_EX 0
destructing DERIVED_EX 0
destructing BASE_EX 0
异常对象没有在第一个 catch
结束时销毁,因为它以 throw
退出,这表明 same 异常对象用于匹配更多 catch
个子句。它不会被复制到一个新的异常对象中,也不会像 throw ex
那样被销毁。
有关规则的详细说明,请参阅cppreference。