Skip to content

Commit 25dd797

Browse files
committed
Update public notes
1 parent baa57f9 commit 25dd797

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed
70.2 KB
Loading

content/编程相关/编程语言/Cpp 之旅 第三版 读书笔记.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1736,5 +1736,187 @@ for (int x : r | views::filter(odd) | views::take(3)) // odd 是谓词
17361736

17371737
标准规定封装的类需要在时间和空间效率上与内置类型的正确使用一样好。
17381738

1739+
### 指针类型
1740+
1741+
#### unique_ptr & shared_ptr
1742+
17391743
![[Pasted image 20251110200542.png]]
17401744

1745+
> **拥有所有权** 的指针意味着它负责删除所指的对象.
1746+
1747+
`<memory>` 中,标准库提供了两个指针来帮助自由存储中的对象:
1748+
1749+
- `unique_ptr` 代表唯一所有权,它的析构函数会销毁它的对象.
1750+
- `shared_ptr` 表示共享所有权,最后一个共享的析构函数负责销毁它的对象.
1751+
1752+
> 请确保确实需要使用共享所有权的时候才使用 shared_ptr.
1753+
1754+
直接构造对象并返回智能指针的函数 `make_shared()` `make_unique()`.
1755+
1756+
*能用容器还是用容器,不能再用智能指针。*
1757+
1758+
> 我们什么时候使用“智能指针”(例如,unique_ptr)而不用资源句柄或者专门为资源设计的操作(例如vector或thread)?不出所料,答案是“当需要指针语义时”。
1759+
1760+
#### span
1761+
1762+
`<span>` 中的 `span`,本质上是一对 (指针, 长度) 值来表示元素序列.
1763+
1764+
span 没有所有权. 类似 `string_view`.
1765+
1766+
*用 span 替代指针序列会好,因为包装过,可以直接用 范围 for,不用太在意范围检查.*
1767+
1768+
不过 span 用下标访问的时候没有范围检查。
1769+
1770+
### 容器
1771+
1772+
有一些特化的容器不完全适配 STL 的框架,不过呼声很高所以在标准库里面。
1773+
1774+
#### array
1775+
1776+
`<array>``array<>` 是定长数组,比起 `vector` 可以存在栈上.
1777+
1778+
使用 array 没有任何额外开销.
1779+
1780+
此外 array 的另一个好处是比起 `[]` 数组可以防止隐式类型转换(因为数组和指针可以直接赋值,在 c 语言里面).
1781+
1782+
#### bitset
1783+
1784+
`bitset<N>` 提供 N bit 序列。
1785+
1786+
这个也是编译时已知的定长序列.
1787+
1788+
#### pair
1789+
1790+
*只是因为常用,而且确实很好用.*
1791+
1792+
`<utility>``pair<T,U>` 表示一对值。
1793+
1794+
pair 的成员变量命名为 first 和 second.
1795+
1796+
`make_pair`
1797+
1798+
#### tuple
1799+
1800+
`tuple` 异构的容器,可以装不同类型。作为零或多个元素的 pair 的泛化.
1801+
1802+
可以用索引访问.
1803+
1804+
```cpp
1805+
string fish = get<0>(t1);
1806+
int count = get<1>(t1);
1807+
double price = get<2>(t1);
1808+
```
1809+
1810+
> 通过索引访问tuple的成员是泛型的、不优雅的,而且容易出错。幸运的是,tuple中具有唯一类型的元素可以通过其类型被“命名”:
1811+
1812+
```cpp
1813+
auto fish = get<string>(t1);
1814+
auto count = get<int>(t1);
1815+
auto price = get<double>(t1);
1816+
```
1817+
1818+
*我能不能这样?*
1819+
1820+
```cpp
1821+
string fish = get(t);
1822+
int count = get(t);
1823+
double price = get(t);
1824+
```
1825+
1826+
tuple 绝大部分用途隐藏在更高级别的框架,比如说结构化绑定访问:
1827+
1828+
```cpp
1829+
auto [fish, count, price] = t1;
1830+
```
1831+
1832+
tuple 真正优势在于必须将未知数量的未知类型元素作为对象存储和运输的时候。
1833+
1834+
### 可变类型容器
1835+
*感觉有点奇怪. 全是模板魔法.*
1836+
1837+
![[Pasted image 20251110213611.png]]
1838+
1839+
### 建议
1840+
1841+
- 库不是很大很复杂才有用.
1842+
- 如果序列可以使用 constexpr 大小,选择 array.
1843+
1844+
#todo
1845+
1846+
## 第18章 并发
1847+
*重量级的一章.*
1848+
1849+
> 标准库并发特性重点提供系统级并发机制的支持,而非直接提供复杂的高层并发模型. 那些高层并发模型,可以基于标准库工具构建,并以库形式提供。
1850+
1851+
> 不要认为并发是灵丹妙药。如果一项任务可以按顺序完成,那么这样做通常更简单、更快捷。因为,将信息从一个线程传递到另一个线程可能会非常昂贵。
1852+
1853+
C++ 支持协程,也就是说,函数可以在调用间保持它们的状态.
1854+
1855+
### 任务task 和 线程thread
1856+
1857+
*再次说明,cout 没有线程安全. 要么只有一个用流,要么用 osyncstream.*
1858+
1859+
C++ 的线程共享单一地址空间。
1860+
1861+
> **定义并发程序的任务时,我们的目标是保持任务的完全隔离,唯一的例外是任务间通信的部分,而这种通信应该以简单而明显的方式进行。思考并发任务的最简单的方式是将它看作一个可以与调用者并发执行的函数。为此,我们只需传递参数、获取结果并保证两者不同时使用共享数据(没有数据竞争)。**
1862+
1863+
thread 产生线程,`join()` 等待线程完成(合并) .
1864+
1865+
`jthread` 是包装后的办法,通过 RAII 来 `join()`.
1866+
1867+
也就是说,`jthread` 会在要析构的时候自动合并线程.
1868+
1869+
由于共享单一地址空间,所以线程可以通过共享对象相互通信,而不用像进程那样通信麻烦。
1870+
1871+
`ref()` 是来自 `<functional>` 的类型函数,可以将变量视为引用而不是对象.
1872+
1873+
*ref() 是引用包装器,能够将对象包装成引用,并且可以被拷贝和存储在容器(实际上就是包了个指针给你用)*
1874+
1875+
返回结果有两种常见的,一种是直接传进去引用直接修改引用,另一种是将所有结果作为单独的参数传递。
1876+
1877+
### 共享数据
1878+
*锁.*
1879+
1880+
#### mutex 和锁
1881+
1882+
`<mutex>`
1883+
1884+
*正常的 lock mutex 就不说了.*
1885+
1886+
注意 RAII 的使用,也就是 `scoped_lock``unique_lock`,比显式锁定和解锁更安全.
1887+
1888+
`socped_lock` 可以防 **多锁**
1889+
1890+
```cpp
1891+
scoped_loc lck {mutex1, mutex2, mutex3};
1892+
```
1893+
1894+
`scoped_lock` 只有在获取所有 mutex 参数后才会继续,而且持有 mutex 的时候不会阻塞.
1895+
1896+
> 通过共享数据进行通信是非常底层的操作。尤其是,程序员必须想办法了解各种任务已经完成和完成的工作。在这方面,使用共享数据不如使用函数调用和返回。另一方面,有些人笃信共享数据一定比拷贝函数参数和返回值更有效率。当涉及大量数据时确实如此,但锁定和解锁是相对昂贵的操作。而且,现代机器非常擅长拷贝数据,尤其是紧凑型数据,例如,vector元素。所以不要因为“效率”而不假思索地选择使用共享数据进行通信,最好先测量再做出选择。
1897+
1898+
共享数据的常见方式之一是多线程读取和单线程写入。`shared_mutex` 支持这种「读写锁」的用法。
1899+
1900+
```cpp
1901+
shared_mutex mx;
1902+
1903+
void reader() {
1904+
shared_lock lck {mx};
1905+
// 读
1906+
}
1907+
1908+
void writer() {
1909+
unique_lock lck {mx};
1910+
// 写
1911+
}
1912+
```
1913+
1914+
#### 原子量
1915+
1916+
> mutex涉及操作系统,算是代价较重的机制。它允许在没有数据竞争的情况下完成任意数量的工作。然而,有一种更简单、更便宜的机制来完成少量工作:atomic变量。
1917+
1918+
*省流就是保证单个变量读写是不可分割的,多线程写也不会发生数据竞争。依赖于底层的原子指令.*
1919+
1920+
1921+
#todo 怎么还有个 lock_guard.
1922+

0 commit comments

Comments
 (0)