Linux
中 tr
命令用于转换或删除文件中的字符。
语法
1 | tr [OPTION] SET1 [SET2] |
选项
1 | -c, --complerment:反选设定字符。也就是符合 SET1 的部份不做处理,不符合的剩余部份才进行转换; |
Item 19: Teat class design as type design.
特征 | new | malloc |
---|---|---|
类型 | 关键字 | 标准库函数 |
支持语言 | C/C++ |
只在 C++ |
申请内存的位置 | 自由存储区 free store |
堆 |
分配内存的大小 | 编译器根据类型信息自行计算 | 显示指定字节数 |
内存分配失败 | 抛出 bac_alloc 异常, 不会返回 NULL |
返回 NULL |
返回类型 | 返回对象类型的指针 | 返回void 指针,需要转换 |
是否调用构造函数/析构函数 | 调用 | 不调用 |
处理数组 | new[] |
手动指定数组的大小 |
是否支持重载 | 支持 | 不支持 |
是否支持内存扩充 | 不支持 | realloc |
内存释放方式 | new/delete , new[]/delete[] |
malloc/free |
Item 18: Make interfaces easy to use correctly and hard to use incorrectly.
Item 17: Store newed objects in smart pointers in standalone statements.
以单独的语句将 new
的对象放入智能指针内。这是为了防止由于其他表达式抛出异常而导致的资源泄漏。
举个栗子:
1 | processWidget(shared_ptr<Widget>(new Widget), priority()); |
上述代码中,在 processWidget
函数被调用之前参数会首先得到计算。可以认为包括三部分的过程:
new Widget
shared_ptr<Widget>
priority()
因为C++不同于其他语言,函数参数的计算顺序很大程度上决定于编译器,编译器认为顺序应当是1, 3, 2,即:
new Widget
priority()
shared_ptr<Widget>
那么如果 priority
抛出了异常,新的 Widget
便永远地找不回来了。虽然我们使用了智能指针,但资源还是泄漏了!
于是更加健壮的实现中,应当将创建资源和初始化智能指针的语句独立出来:
1 | shared_ptr<Widget> pw = shared_ptr<Widget>(new Widget); |
Item 16: Use the same form in corresponding uses of new and delete.
如果你用 new
申请了动态内存,请用 delete
来销毁;如果你用 new xx[]
申请了动态内存,请用 delete[]
来销毁:
举个栗子:
1 | std::string* stringPtrl = new std::string; |
上面很容易理解但需要注意typedef
:
1 | typedef std::string AddressLines[4]; //每个人的地址有四行, |
由于 AddressLines
是个数组,如果这样使用 new
:
1 | std::string *pal = new AddressLines; //注意. "new AddressLines" 返回 |
那就必须匹配 “数组形式“的 delete
:
1 | delete pal; //行为未有定义! |
为避免诸如此类的错误,最好尽量不要对数组形式做 typedefs
动作。可以使用更加面向对象的vector
、string
等对象。
Item 15: Provide access to raw resources in resource-managing classes.
APIs
往往要求访问原始资源(raw resources
),所以每一个RAII class 应该提供提供对原始资源访问的方法。获取资源的方式有两类:隐式地获取和显式地获取。 显式的资源获取会更安全,它最小化了无意中进行类型转换的机会。
shared_ptr
提供了 get
方法来得到资源。
1 | shared_ptr<Investment> pInv; |
为了让 pInv
表现地更像一个指针,shared_ptr
还重载了解引用运算符(dereferencing operator
) operator->
和 operator*
:
1 | class Investment{ |
我们封装了Font来管理资源:
1 | class Font{ |
通过get方法来访问FontHandle:
1 | Font f(getFont()); |
可以隐式类型转换运算符将 Font
转换为 FontHandle
:
1 | class Font{ |
然而问题也随之出现:
1 | FontHandle h2 = f1; |
无意间 h2
并没有被资源管理起来,这将会引发意外的资源泄漏。所以隐式转换在提供便利的同时, 也引起了资源泄漏的风险。
Item 14: Think carefully about copying behavior in resource-managing classes.
设计一个 RAII
对象:
1 | class Lock { |
客户对Lock
的使用:
1 | Mutex m; |
当一个 RAII
对象被复制,会发生什么事? 不确定?
1 | Lock ml1(&m); |
记住资源管理对象的拷贝行为取决于资源本身的拷贝行为,同时资源管理对象也可以根据业务需要来决定自己的拷贝行为。一般有如下四种方式:
禁止复制。参考若不想使用编译器自动生成的函数,就该明确拒绝。对Lock而言看起来是这样:
1 | class Lock : private Uncopyable { |
引用计数,采用 shared_ptr
的逻辑。shared_ptr
构造函数提供了第二个参数 deleter
,当引用计数到 0
时被调用。 所以 Lock
可以通过聚合一个 shared_ptr
成员来实现引用计数:
1 | class Lock{ |
Lock
的析构会引起 mutexPtr
的析构,而 mutexPtr
计数到0时unlock(mutexPtr.get())
会被调用。
拷贝底部资源。复制资源管理对象时,进行的是深拷贝。比如 string
的行为:内存存有指向对空间的指针,当它被复制时会复制那片空间。
转移底部资源的拥有权。auto_ptr
就是这样做的,把资源移交给另一个资源管理对象,自己的资源置空。
Item 13: Use objects to manage resources.
Item 12: Copy all parts of an object
正确拷贝函数实现:
1 | class Customer{ |
1 | class Customer{ |
这时 lastTransaction
便被你忽略了,编译器也不会给出任何警告(即使在最高警告级别)
1 | class PriorityCustomer: public Customer { |
正确写法:
1 | class PriorityCustomer: public Customer { |