-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
56ca785
commit 660fdfc
Showing
5 changed files
with
369 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
************************************************************************************************************************ | ||
硬核题: 抛出一个构造函数和析构函数会输出的类 | ||
************************************************************************************************************************ | ||
|
||
大概率不考, 但理论很简单, 建议了解一下. | ||
|
||
======================================================================================================================== | ||
异常抛出的对象 | ||
======================================================================================================================== | ||
|
||
异常抛出时是要抛出一个对象的, 那么这个对象应该存放在哪? | ||
|
||
显然不能存放在栈上, 因为 :doc:`normal` 中已经指出, 在栈回溯的过程中, 这些栈上的自动存储对象都需要被析构. | ||
所以当以 :cpp:`throw object;` 抛出时, 会调用拷贝构造函数创建一个新的对象, 它不存放在栈中. | ||
|
||
直到异常处理完成, 这个异常抛出的对象才会被析构. | ||
|
||
.. code-block:: cpp | ||
:linenos: | ||
void function() { | ||
try { | ||
Printer printer{Info{.ctor = "i", .copy_ctor = "n", .dtor = "t"}}; | ||
throw printer; | ||
} catch (Printer& printer) { | ||
Printer another{Info{.ctor = "8", .dtor = "_"}}; | ||
} | ||
} | ||
auto main() -> int { | ||
function(); | ||
} | ||
.. admonition:: 点击查看提示 | ||
:class: dropdown | ||
|
||
6 个字符, 大小固定为 8 位的有符号整数类型. | ||
|
||
.. admonition:: 点击查看答案 | ||
:class: dropdown, solution | ||
|
||
:godbolt:`E1dezb8eP`, 答案: :code:`int8_t`. | ||
|
||
======================================================================================================================== | ||
异常捕获时 | ||
======================================================================================================================== | ||
|
||
假设抛出了类型 :cpp:`Printer`, | ||
|
||
- 如果 :cpp:`catch` 中写的是 :cpp:`catch (Printer printer)`, 则还需要发生拷贝. | ||
- 如果 :cpp:`catch` 中写的是 :cpp:`catch (Printer& printer)`, 则直接引用异常抛出的对象. | ||
|
||
.. code-block:: cpp | ||
:linenos: | ||
void function() { | ||
try { | ||
Printer printer{Info{.ctor = "i", .copy_ctor = "n", .dtor = "t"}}; | ||
throw printer; | ||
} catch (Printer& printer) { | ||
Printer another{Info{.ctor = "8", .dtor = "_"}}; | ||
} | ||
} | ||
auto main() -> int { | ||
function(); | ||
} | ||
.. admonition:: 点击查看提示 | ||
:class: dropdown | ||
|
||
8 个字符, 没能编出什么有意义的词了😭. | ||
|
||
.. admonition:: 点击查看答案 | ||
:class: dropdown, solution | ||
|
||
:godbolt:`7Wz5f4jvY`, 答案: :code:`intn8_tt`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
.. _exception_throwing: | ||
|
||
######################################################################################################################## | ||
异常的抛出与捕获 | ||
######################################################################################################################## | ||
|
||
.. warning:: | ||
|
||
这里所有内容都可以归结为一句话: :doc:`异常抛出仍会恰当地进行析构, 从而避免资源泄露 </faq/basic_concepts/resource>`. 因此实际进行异常处理时, **完全不需要考虑这些**, 而更应该考虑 `异常保证等级 <https://zh.cppreference.com/w/cpp/language/exceptions#.E5.BC.82.E5.B8.B8.E5.AE.89.E5.85.A8>`_ 等内容. | ||
|
||
但考试就是爱考这个, 而更为重要、与写好代码更相关的 *异常保证等级* 完全不在应试教学范围内. | ||
|
||
.. toctree:: | ||
:maxdepth: 1 | ||
:caption: 目录 | ||
|
||
preface.rst | ||
normal.rst | ||
hardcore.rst |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
************************************************************************************************************************ | ||
正常题: 抛出时栈回溯 (stack unwinding) | ||
************************************************************************************************************************ | ||
|
||
抛出异常时, 控制流沿着栈 **向上** 进行回溯, 直到找到一个能 **匹配** 到的 :cpp:`try-catch` 块, 如果没能找到, 则调用 :cpp:`std::terminate` 终止程序. | ||
|
||
自动存储期对象是分配在栈上的 (:ref:`具体地, 分配在代码块 {} 之内 <question_board:lifetime>`), 因此当抛出异常而发生栈回溯时, 若栈回溯离开了代码块 (函数代码块、:cpp:`try-catch` 块、自行添加的 :cpp:`{}` 等), 则会依次析构 *已构造但尚未销毁的自动存储期对象*, 以它们的构造函数完成的逆序调用析构函数. | ||
|
||
.. hint:: | ||
|
||
如果不理解什么是栈, 请进行 :ref:`断点调试 <question_board:debugger>`, 其中: | ||
|
||
- 调用堆栈显示的就是栈的内容, 各行是对应函数调用的记录 (如局部变量等), 称为栈帧. 如果对递归函数进行断点调试就会发现, 每次调用都创建了新的栈帧, 即可以理解为单纯就是调用了新的函数, 所以递归函数的调用与普通函数的调用是完全一致的. | ||
- 局部变量窗口显示的就是栈帧中的局部变量内容, 因此当栈回溯过程离开了该局部变量所在的代码块, 自然会析构掉其中 *已构造但尚未销毁的自动存储期对象*. | ||
|
||
======================================================================================================================== | ||
题 1 | ||
======================================================================================================================== | ||
|
||
.. code-block:: cpp | ||
:linenos: | ||
void function() { | ||
Printer c1{Info{.ctor = "c", .dtor = "n"}}; | ||
try { | ||
throw 1; | ||
} catch (int) { | ||
std::cout << "i"; | ||
} | ||
} | ||
auto main() -> int { | ||
function(); | ||
} | ||
.. admonition:: 点击查看提示 | ||
:class: dropdown | ||
|
||
3 个字符, C++ 标准输入流. | ||
|
||
.. admonition:: 点击查看答案 | ||
:class: dropdown, solution | ||
|
||
:godbolt:`ThTaW6qnM`, 答案: :code:`cin`. | ||
|
||
======================================================================================================================== | ||
题 2 | ||
======================================================================================================================== | ||
|
||
:cpp:`try-catch` 块也是代码块. | ||
|
||
.. code-block:: cpp | ||
:linenos: | ||
void function() { | ||
Printer c1{Info{.ctor = "s", .dtor = "n"}}; | ||
try { | ||
Printer c2{Info{.ctor = "t", .dtor = "r"}}; | ||
throw 1; | ||
} catch (int) { | ||
Printer c3{Info{.ctor = "l", .dtor = "e"}}; | ||
} | ||
} | ||
auto main() -> int { | ||
function(); | ||
} | ||
.. admonition:: 点击查看提示 | ||
:class: dropdown | ||
|
||
6 个字符, 获取 C 风格字符串的长度. | ||
|
||
.. admonition:: 点击查看答案 | ||
:class: dropdown, solution | ||
|
||
:godbolt:`vMo9Kv6K6`, 答案: :code:`strlen`. | ||
|
||
======================================================================================================================== | ||
题 3 | ||
======================================================================================================================== | ||
|
||
.. code-block:: cpp | ||
:linenos: | ||
void function1() { | ||
Printer c1{Info{.ctor = "O", .dtor = "F"}}; | ||
throw 1.0; | ||
} | ||
void function2() { | ||
Printer* c1 = new Printer{Info{.ctor = "E", .dtor = "I"}}; | ||
function1(); | ||
Printer c2{Info{.ctor = "H", .dtor = "L"}}; | ||
} | ||
auto main() -> int { | ||
try { | ||
function2(); | ||
} catch (double) { | ||
} | ||
} | ||
.. admonition:: 点击查看提示 | ||
:class: dropdown | ||
|
||
3 个字符, 输入流读取到流结束时产生的标志. | ||
|
||
.. admonition:: 点击查看答案 | ||
:class: dropdown, solution | ||
|
||
:godbolt:`6nvf1bj5j`, 答案: :code:`EOF`. | ||
|
||
======================================================================================================================== | ||
题 4 | ||
======================================================================================================================== | ||
|
||
:cpp:`catch` 块判断是否匹配时, 与判断函数是否匹配的方式不同, 不正式地说, 它不会进行大部分隐式类型转换. | ||
|
||
.. code-block:: cpp | ||
:linenos: | ||
void function1() { | ||
try { | ||
Printer c1{Info{.ctor = "i", .dtor = "n"}}; | ||
throw 1; | ||
} catch (double) { | ||
} | ||
} | ||
void function2() { | ||
Printer* c1 = new Printer{Info{.ctor = "c", .dtor = "u"}}; | ||
function1(); | ||
Printer c2{Info{.ctor = "o", .dtor = "t"}}; | ||
} | ||
auto main() -> int { | ||
try { | ||
function2(); | ||
} catch (int) { | ||
} | ||
} | ||
.. admonition:: 点击查看提示 | ||
:class: dropdown | ||
|
||
3 个字符, 标准输入流. | ||
|
||
.. admonition:: 点击查看答案 | ||
:class: dropdown, solution | ||
|
||
:godbolt:`7TEqd7arP`, 答案: :code:`cin`. | ||
|
||
======================================================================================================================== | ||
题 5 | ||
======================================================================================================================== | ||
|
||
:cpp:`catch` 块判断是否匹配时, 异常对象可以与其 **公用基类** 匹配. | ||
|
||
.. code-block:: cpp | ||
:linenos: | ||
class Base {}; | ||
class Derived : public Base {}; | ||
void function1() { | ||
try { | ||
Printer c1{Info{.ctor = "r", .dtor = "o"}}; | ||
throw Derived{}; | ||
} catch (Base&) { | ||
} | ||
} | ||
void function2() { | ||
Printer* c1 = new Printer( Info{.ctor = "f", .dtor = "z"} ); | ||
function1(); | ||
Printer c2(Info{.ctor = "n", .dtor = "t"}); | ||
} | ||
auto main() -> int { | ||
try { | ||
function2(); | ||
} catch (Derived&) { | ||
} | ||
} | ||
.. admonition:: 点击查看提示 | ||
:class: dropdown | ||
|
||
5 个字符, 如何获取 vector 的首元素? | ||
|
||
.. admonition:: 点击查看答案 | ||
:class: dropdown, solution | ||
|
||
:godbolt:`7nzo55zPv`, 答案: :code:`front`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
************************************************************************************************************************ | ||
题目中所使用的类 | ||
************************************************************************************************************************ | ||
|
||
为了提高可读性, 部分地方采用了 C++20 的语法进行初始化, 见下文. | ||
|
||
.. code-block:: text | ||
:linenos: | ||
ctor: 非拷贝构造时输出的字符 | ||
copy_ctor: 拷贝构造时输出的字符 | ||
copy_assign: 拷贝赋值时输出的字符 | ||
dtor: 析构时输出的字符 | ||
======================================================================================================================== | ||
:cpp:`class Printer` | ||
======================================================================================================================== | ||
|
||
在非拷贝构造, 被用于拷贝构造, 被用于拷贝赋值, 析构时输出对应的字符串. | ||
|
||
例如, | ||
|
||
.. code-block:: cpp | ||
:caption: 构造析构 | ||
auto main() -> int { | ||
// 构造时输出 "0", 被用于拷贝构造时输出 "1", 被用于拷贝赋值时输出 "2", 析构时输出 "3", 其余情况不输出 | ||
Printer c1{Info{.ctor = "0", .copy_ctor = "1", .copy_assign = "2", .dtor = "3"}}; | ||
// 构造时输出 "4", 析构时输出 "5", 其余情况不输出 | ||
Printer c2{Info{.ctor = "4", .dtor = "5"}}; | ||
} | ||
// 最终输出 | ||
// 0: c1 构造 | ||
// 4: c2 构造 | ||
// 5: c2 析构 | ||
// 3: c1 析构 | ||
.. code-block:: cpp | ||
:caption: 拷贝构造 | ||
auto main() -> int { | ||
// 构造时输出 "0", 被用于拷贝构造时输出 "1", 被用于拷贝赋值时输出 "2", 析构时输出 "3", 其余情况不输出 | ||
Printer c1{Info{.ctor = "0", .copy_ctor = "1", .copy_assign = "2", .dtor = "3"}}; | ||
// 拷贝构造输出 "1", 并且之后 c2.info == c1.info | ||
// 即此后构造时输出 "0", 被用于拷贝构造时输出 "1", 被用于拷贝赋值时输出 "2", 析构时输出 "3", 其余情况不输出 | ||
Printer c2{c1}; | ||
} | ||
// 最终输出 | ||
// 0: c1 构造 | ||
// 1: Printer c2{c1} | ||
// 3: c2 析构 | ||
// 3: c1 析构 | ||
.. code-block:: cpp | ||
:caption: 拷贝赋值 | ||
auto main() -> int { | ||
// 构造时输出 "0", 被用于拷贝构造时输出 "1", 被用于拷贝赋值时输出 "2", 析构时输出 "3", 其余情况不输出 | ||
Printer c1{Info{.ctor = "0", .copy_ctor = "1", .copy_assign = "2", .dtor = "3"}}; | ||
// 构造时输出 "4", 被用于拷贝构造时输出 "5", 被用于拷贝赋值时输出 "6", 析构时输出 "7", 其余情况不输出 | ||
Printer c2{Info{.ctor = "4", .copy_ctor = "5", .copy_assign = "6", .dtor = "7"}}; | ||
// 输出 "2", 并且之后 c2.info == c1.info | ||
// 即此后构造时输出 "0", 被用于拷贝构造时输出 "1", 被用于拷贝赋值时输出 "2", 析构时输出 "3", 其余情况不输出 | ||
c2 = c1; | ||
} | ||
// 最终输出 | ||
// 0: c1 构造 | ||
// 4: c2 构造 | ||
// 2: c2 = c1; | ||
// 3: c2 析构 | ||
// 3: c1 析构 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters