Skip to content

Commit 0f6cac0

Browse files
committed
deploy: d159aa0
1 parent 22549eb commit 0f6cac0

File tree

5 files changed

+141
-5
lines changed

5 files changed

+141
-5
lines changed

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ <h2 id="_1">前言</h2>
292292
<blockquote>
293293
<p><img src="./img/bulb.png" height="30px" width="auto" style="margin: 0; border: none"/> 本书还在持续更新中……要追番的话,可以在 <a href="https://github.com/parallel101/cppguidebook">GitHub</a> 点一下右上角的 “Watch” 按钮,每当小彭老师提交新 commit,GitHub 会向你发送一封电子邮件,提醒你小彭老师更新了。</p>
294294
</blockquote>
295-
<p>更新时间:2024年09月09日 17:24:48 (UTC+08:00)</p>
295+
<p>更新时间:2024年09月13日 12:37:56 (UTC+08:00)</p>
296296
<p><a href="https://parallel101.github.io/cppguidebook">在 GitHub Pages 浏览本书</a> | <a href="https://142857.red/book">在小彭老师自己维护的镜像上浏览本书</a></p>
297297
<h2 id="_2">格式约定</h2>
298298
<blockquote>

no_more_new/index.html

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ <h2 id="raii-delete">RAII 比起手动 delete 的优势</h2>
430430
<blockquote>
431431
<p><img src="../img/question.png" height="30px" width="auto" style="margin: 0; border: none"/><a href="../error_code/">错误处理专题</a>中有进一步的详解。</p>
432432
</blockquote>
433-
<p>然而这有时我们会忘记在提前返回的分支中 delete 之前分配过的所有智能指针</p>
433+
<p>然而这有时我们会忘记在提前返回的分支中 delete 之前分配过的所有指向动态内存的指针</p>
434434
<pre><code class="language-cpp">int func() {
435435
Foo *foo = new Foo();
436436
...
@@ -443,6 +443,74 @@ <h2 id="raii-delete">RAII 比起手动 delete 的优势</h2>
443443
return 0;
444444
}
445445
</code></pre>
446+
<p>过去,人们使用 <code>goto</code> 大法拙劣地在提前返回时 <code>delete</code> 动态内存:</p>
447+
<pre><code class="language-cpp">int main() {
448+
Foo *foo1, *foo2;
449+
int ret = 0;
450+
foo1 = new Foo();
451+
...
452+
if (出错) {
453+
ret = -1;
454+
goto out_foo1;
455+
}
456+
...
457+
Foo *foo2 = new Foo();
458+
...
459+
if (出错) {
460+
ret = -2;
461+
goto out_foo2;
462+
}
463+
...
464+
out_foo2: delete foo2;
465+
out_foo1: delete foo1;
466+
return ret;
467+
}
468+
</code></pre>
469+
<p>这对于“写”程序的人,其实还不算什么,无非就是注意匹配,反正都是一次性写完就得了。</p>
470+
<p>真正遭罪的是“改”程序的人,如果他要删掉foo1,那么他需要在两个地方来回跳转,如果foo1变成 <code>new[]</code> 了,那么他需要跳到下面把 <code>delete foo1</code> 也改成 <code>delete[] foo1</code>。如果foo1要改名,那么还需要跳到下面 <code>out_foo1:</code> 标签也改了。如果要新增一个foo3指针,那还需要跳到上面加个 <code>Foo *foo3;</code>,下面加个标签和 delete。</p>
471+
<blockquote>
472+
<p><img src="../img/awesomeface.png" height="30px" width="auto" style="margin: 0; border: none"/> BUG漫漫其修远兮,吾将上下而求索。</p>
473+
</blockquote>
474+
<p>你是否遇到过写程序梭哈,事后“上下求索”的情况?同一个变量 foo 的生命周期被极限撕扯,分居两地,极大的妨碍了我们改程序的效率。</p>
475+
<p>而统计表明,程序员10%的时间用于写代码,90%的时间花在改代码上。节约改代码的时间,就是节约程序员90%的生命。改代码不容易出错,可以省去软件中90%的BUG。</p>
476+
<p>所以,除非你是一次性交差项目不打算更新了,或者确认了改代码的人不会是你,否则必然要用包括智能指针、设计模式在内的各种手段竭力避免代码分散化。</p>
477+
<p>在一个庞大的代码系统中看到原始指针是最头疼的:</p>
478+
<pre><code class="language-cpp">Student *getstu(const char *name);
479+
</code></pre>
480+
<p>没有任何信息告诉我:</p>
481+
<ol>
482+
<li>这个指针指向对象生命周期如何?</li>
483+
<li>要我负责释放内存吗?</li>
484+
<li>如何释放,<code>delete</code><code>delete[]</code><code>free</code>、还是 <code>fclose</code></li>
485+
<li>可以是空指针吗?</li>
486+
<li>数组还是单个对象?</li>
487+
<li>如果是数组,那么长度多少?是C风格0结尾字符串吗?</li>
488+
<li>是否移交所有权?</li>
489+
<li>共享或独占?</li>
490+
<li>该资源可以拷贝吗?</li>
491+
<li>如果可以,如何拷贝?</li>
492+
</ol>
493+
<p>而你只能通过查阅文档,才能确认这些信息,拖慢了开发效率。</p>
494+
<p>而使用 RAII 容器(不仅是只能指针)作为类型,就能让人一眼就看出以上10个信息。</p>
495+
<pre><code class="language-cpp">gsl::not_null&lt;std::unique_ptr&lt;Student&gt;&gt; getstu1(std::string_view name);
496+
Student &amp;getstu2(std::string_view name);
497+
</code></pre>
498+
<p>以上代码中,一看就明白,getstu1会移交所有权,且不可能为空;getstu2不转移所有权,仅仅只是提供给调用者访问,且不可能为空。</p>
499+
<p>传统指针因为语义不明确,功能多样化,有用错的可能,例如用户可能偷懒不看文档,就擅自瞎写:</p>
500+
<pre><code class="language-cpp">char name;
501+
Student *stu = getstu(&amp;name);
502+
if (stu == NULL) exit(1);
503+
free(stu);
504+
</code></pre>
505+
<p>实际上 <code>getstu</code> 的参数 <code>name</code> 需要是一个 C 风格 0 结尾字符串,用户却不小心当作单个 <code>char</code> 的指针写了,编译器没有报错。</p>
506+
<p><code>stu</code> 实际上是 <code>getstu</code> 返回给调用者的临时引用,并不移交所有权,而用户却想当然的释放了。</p>
507+
<p>并且实际上 <code>getstu</code> 从不返回空指针,用户根本不用提心吊胆地检查。</p>
508+
<p>一个优质的函数接口,就不应该给用户这种犯错的机会。</p>
509+
<p>我知道,你会说,<code>std::string</code> 不也能从 <code>&amp;name</code> 构造,放任编译通过,不也会犯错吗?</p>
510+
<p>是这样的,你甚至可以从 <code>0</code> 构造 <code>std::string</code>,编译一样通过,程序会直接崩溃:</p>
511+
<pre><code class="language-cpp">std::string s = 0; // 0 被当作 NULL,从而调用构造函数 std::string(const char *)
512+
</code></pre>
513+
<p>标准库里不符合小彭老师设计模式的多了去了,标准库的垃圾是历史遗留问题,不是小彭老师的问题。</p>
446514
<p>而智能指针,不论是提前返回还是最终的返回,只要是函数结束了,都能自动释放。智能指针使得程序员写出“提前返回式”毫无精神压力,再也不用惦记着哪些需要释放。</p>
447515
<pre><code class="language-cpp">int func() {
448516
shared_ptr&lt;Foo&gt; foo = make_shared&lt;Foo&gt;();

print_page/index.html

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ <h2 id="index-_1">前言</h2>
421421
<blockquote>
422422
<p><img src="../img/bulb.png" height="30px" width="auto" style="margin: 0; border: none"/> 本书还在持续更新中……要追番的话,可以在 <a href="https://github.com/parallel101/cppguidebook">GitHub</a> 点一下右上角的 “Watch” 按钮,每当小彭老师提交新 commit,GitHub 会向你发送一封电子邮件,提醒你小彭老师更新了。</p>
423423
</blockquote>
424-
<p>更新时间:2024年09月09日 17:24:48 (UTC+08:00)</p>
424+
<p>更新时间:2024年09月13日 12:37:56 (UTC+08:00)</p>
425425
<p><a href="https://parallel101.github.io/cppguidebook">在 GitHub Pages 浏览本书</a> | <a href="https://142857.red/book">在小彭老师自己维护的镜像上浏览本书</a></p>
426426
<h2 id="index-_2">格式约定</h2>
427427
<blockquote>
@@ -6098,7 +6098,7 @@ <h2 id="no_more_new-raii-delete">RAII 比起手动 delete 的优势</h2>
60986098
<blockquote>
60996099
<p><img src="../img/question.png" height="30px" width="auto" style="margin: 0; border: none"/> 在<a href="#error_code">错误处理专题</a>中有进一步的详解。</p>
61006100
</blockquote>
6101-
<p>然而这有时我们会忘记在提前返回的分支中 delete 之前分配过的所有智能指针。</p>
6101+
<p>然而这有时我们会忘记在提前返回的分支中 delete 之前分配过的所有指向动态内存的指针。</p>
61026102
<pre><code class="language-cpp">int func() {
61036103
Foo *foo = new Foo();
61046104
...
@@ -6111,6 +6111,74 @@ <h2 id="no_more_new-raii-delete">RAII 比起手动 delete 的优势</h2>
61116111
return 0;
61126112
}
61136113
</code></pre>
6114+
<p>过去,人们使用 <code>goto</code> 大法拙劣地在提前返回时 <code>delete</code> 动态内存:</p>
6115+
<pre><code class="language-cpp">int main() {
6116+
Foo *foo1, *foo2;
6117+
int ret = 0;
6118+
foo1 = new Foo();
6119+
...
6120+
if (出错) {
6121+
ret = -1;
6122+
goto out_foo1;
6123+
}
6124+
...
6125+
Foo *foo2 = new Foo();
6126+
...
6127+
if (出错) {
6128+
ret = -2;
6129+
goto out_foo2;
6130+
}
6131+
...
6132+
out_foo2: delete foo2;
6133+
out_foo1: delete foo1;
6134+
return ret;
6135+
}
6136+
</code></pre>
6137+
<p>这对于“写”程序的人,其实还不算什么,无非就是注意匹配,反正都是一次性写完就得了。</p>
6138+
<p>真正遭罪的是“改”程序的人,如果他要删掉foo1,那么他需要在两个地方来回跳转,如果foo1变成 <code>new[]</code> 了,那么他需要跳到下面把 <code>delete foo1</code> 也改成 <code>delete[] foo1</code>。如果foo1要改名,那么还需要跳到下面 <code>out_foo1:</code> 标签也改了。如果要新增一个foo3指针,那还需要跳到上面加个 <code>Foo *foo3;</code>,下面加个标签和 delete。</p>
6139+
<blockquote>
6140+
<p><img src="../img/awesomeface.png" height="30px" width="auto" style="margin: 0; border: none"/> BUG漫漫其修远兮,吾将上下而求索。</p>
6141+
</blockquote>
6142+
<p>你是否遇到过写程序梭哈,事后“上下求索”的情况?同一个变量 foo 的生命周期被极限撕扯,分居两地,极大的妨碍了我们改程序的效率。</p>
6143+
<p>而统计表明,程序员10%的时间用于写代码,90%的时间花在改代码上。节约改代码的时间,就是节约程序员90%的生命。改代码不容易出错,可以省去软件中90%的BUG。</p>
6144+
<p>所以,除非你是一次性交差项目不打算更新了,或者确认了改代码的人不会是你,否则必然要用包括智能指针、设计模式在内的各种手段竭力避免代码分散化。</p>
6145+
<p>在一个庞大的代码系统中看到原始指针是最头疼的:</p>
6146+
<pre><code class="language-cpp">Student *getstu(const char *name);
6147+
</code></pre>
6148+
<p>没有任何信息告诉我:</p>
6149+
<ol>
6150+
<li>这个指针指向对象生命周期如何?</li>
6151+
<li>要我负责释放内存吗?</li>
6152+
<li>如何释放,<code>delete</code>、<code>delete[]</code>、<code>free</code>、还是 <code>fclose</code>?</li>
6153+
<li>可以是空指针吗?</li>
6154+
<li>数组还是单个对象?</li>
6155+
<li>如果是数组,那么长度多少?是C风格0结尾字符串吗?</li>
6156+
<li>是否移交所有权?</li>
6157+
<li>共享或独占?</li>
6158+
<li>该资源可以拷贝吗?</li>
6159+
<li>如果可以,如何拷贝?</li>
6160+
</ol>
6161+
<p>而你只能通过查阅文档,才能确认这些信息,拖慢了开发效率。</p>
6162+
<p>而使用 RAII 容器(不仅是只能指针)作为类型,就能让人一眼就看出以上10个信息。</p>
6163+
<pre><code class="language-cpp">gsl::not_null&lt;std::unique_ptr&lt;Student&gt;&gt; getstu1(std::string_view name);
6164+
Student &amp;getstu2(std::string_view name);
6165+
</code></pre>
6166+
<p>以上代码中,一看就明白,getstu1会移交所有权,且不可能为空;getstu2不转移所有权,仅仅只是提供给调用者访问,且不可能为空。</p>
6167+
<p>传统指针因为语义不明确,功能多样化,有用错的可能,例如用户可能偷懒不看文档,就擅自瞎写:</p>
6168+
<pre><code class="language-cpp">char name;
6169+
Student *stu = getstu(&amp;name);
6170+
if (stu == NULL) exit(1);
6171+
free(stu);
6172+
</code></pre>
6173+
<p>实际上 <code>getstu</code> 的参数 <code>name</code> 需要是一个 C 风格 0 结尾字符串,用户却不小心当作单个 <code>char</code> 的指针写了,编译器没有报错。</p>
6174+
<p>而 <code>stu</code> 实际上是 <code>getstu</code> 返回给调用者的临时引用,并不移交所有权,而用户却想当然的释放了。</p>
6175+
<p>并且实际上 <code>getstu</code> 从不返回空指针,用户根本不用提心吊胆地检查。</p>
6176+
<p>一个优质的函数接口,就不应该给用户这种犯错的机会。</p>
6177+
<p>我知道,你会说,<code>std::string</code> 不也能从 <code>&amp;name</code> 构造,放任编译通过,不也会犯错吗?</p>
6178+
<p>是这样的,你甚至可以从 <code>0</code> 构造 <code>std::string</code>,编译一样通过,程序会直接崩溃:</p>
6179+
<pre><code class="language-cpp">std::string s = 0; // 0 被当作 NULL,从而调用构造函数 std::string(const char *)
6180+
</code></pre>
6181+
<p>标准库里不符合小彭老师设计模式的多了去了,标准库的垃圾是历史遗留问题,不是小彭老师的问题。</p>
61146182
<p>而智能指针,不论是提前返回还是最终的返回,只要是函数结束了,都能自动释放。智能指针使得程序员写出“提前返回式”毫无精神压力,再也不用惦记着哪些需要释放。</p>
61156183
<pre><code class="language-cpp">int func() {
61166184
shared_ptr&lt;Foo&gt; foo = make_shared&lt;Foo&gt;();

search/search_index.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

sitemap.xml.gz

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)