Effective C++ 笔记
Effective C++ 笔记
- Effective C++ 笔记
- Sec0 Introduction
- Sec1 Accustoming Yourself to C++
- Sec2 Constructors, Destructors, and Assignment Operators
- Item05: Know what functions C++ silently writes and calls
- Item06: Explicitly disallow the use of compiler-generated functions you do not want
- Item07:Declare destructors virtual in polymorphic base classes
- Item08: Prevent exceptions from leaving destructions
- Item09: Never call virtual functions during construction or destruction
- Item10: Have assignment operators return a reference to *this
- Item11: Handle assignment to self in operator=
- Item12: Copy all parts of an object
- Sec03: Resource Management
- Item13: Use objects to manage resource
- Item14: Think carefully about copying behavior in resource-managing class
- Item15: Provide access to raw resources in resource-managing classes
- Item16: Use the same form in corresponding uses of new and delete
- Item17: Store newed objects in smart pointers in standalone statements
- Sec4 Designs and Declarations
Sec0 Introduction
- 本书的目的:
如何有效运用C++,使软件易理解、易维护、可移植、可扩充、高效、并有预期行为 - 提出的忠告分两类:
- 一般性的设计策略,带有具体细节的特定语言特性
- 如何在两个不同做法中择一完成某项任务?
- inheritance还是templates?
- public还是private?
- private继承还是composition?
- 选择member函数还是non-member函数?
- 选择pass-by-value还是pass-by-reference?
- 更多的细节:
- assignment操作符的适当返回类型
- 何时该令析构函数为virtual?
- 当operator new无法找到足够内存该怎么办?
- 一般性的设计策略,带有具体细节的特定语言特性
- 阅读本书的方式:
- 从感兴趣的items开始
Sec1 Accustoming Yourself to C++
Item01: View C++ as a federation of languages
-
现在的C++: Multiparadigm programming language
一个同时支持procedural, object-oriented, functional, generic, metaprogramming的语言 -
如何理解这样一个语言?
将C++视为一个由相关语言组成的联邦而非单一语言。在其某个次语言(sublanguage)中,各种守则与通例都倾向于简单、直观易懂并且容易记住。
主要的次语言有4个:- C
blocks、statements、preprocessor、built-in data types、arrays、pointers等统统来自C - Object-Oriented C++
即C with Classes 所诉求的:classes(析构函数和构造函数)、encapsulation、inheritance、polymorphism、virtual函数(动态绑定)等等。 - Template C++
即C++的泛型编程(generic programming) 部分。带来了崭新的编程范型(programming paradigm),也就是所谓的template metaprogramming(TMP)。 - STL
template程序库。定义了containers、iterators、algorithms和function objects。
- C
-
Tips:
C++高效编程守则视状况而变化,取决于你使用C++的哪个部分。
Iterm02: Prefer consts, and inlines to #defines
-
使用常量来替换宏定义
-
使用
#define
的坏处:#define ASPECT_RATIO 1.653
- 可能编译器开始处理源码之前就被预处理器移走了。于是记号名称
ASPECT_RATIO
有可能没有进入记号表(symbol table) - 所以运用此常量但是获得编译错误信息的时候,难以定位。
- 可能编译器开始处理源码之前就被预处理器移走了。于是记号名称
-
解决之道:用常量来替换宏
const double AspectRatio = 1.653;
- 可以进入记号表(symbol table)内。
-
-
替换的两种特殊情况:
-
定义常量指针(constant pointers)
要在头文件定义一个常量cahr*-based字符串。必须写 const两次:
const char* const authorName = "Scott Meyers";
其实string对象更合适
const std::string authorName("Scott Meyers");
-
class专属常量:
为了将常量的作用域(scope)限制于class内,必须让它成为class的一个成员(member):而为确保此常量最多只有一份实体,你必须让它称为一个static成员:class GamePlayer { private: static const int NumTurns = 5; // 常量声明式 int scores[NumTurns]; ... };
-
注1:
变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。
变量声明:用于向程序表明变量的类型和名字。
定义也是声明:当定义变量时我们声明了它的类型和名字。
extern关键字:通过使用extern关键字声明变量名而不定义它。
-
注2:
1.定义也是声明,extern声明不是定义,即不分配存储空间。extern告诉编译器变量在其他地方定义了。
2.如果声明有初始化式,就被当作定义,即使前面加了extern。只有当extern声明位于函数外部时,才可以被初始化。
例如:extern double pi=3.1416; //定义
3.函数的声明和定义区别比较简单,带有{ }的就是定义,否则就是声明。
4.除非有extern关键字,否则都是变量的定义。
-
注3:程序设计风格:
1. 不要把变量定义放入.h文件,这样容易导致重复定义错误。
2. 尽量使用static关键字把变量定义限制于该源文件作用域,除非变量被设计成全局的。
3. 可以在头文件中声明一个变量,在用的时候包含这个头文件就声明了这个变量。
只要不取地址,可以声明并使用它们而无须提供定义式。
但如果取某个class专属常量的地址,要看到定义式的话,需要这么写:const int GamePlayer::NumTurns; // 定义
因为class常量在声明时获得初值。因此定义时不可以再设初值。
-
-
也可以用enum来代替const或者define。但是enum行为上更像一个define
-
-
宏作为函数需要注意的地方:
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b)) int a = 5, b = 0; CALL_WITH_MAX(++a, b); // 递增两次 CALL_WITH_MAX(++a, b+10); //递增一次
所以建议用template inline函数:
template<typename T> inline void callWithMax(const T& a, const T& b) { f(a > b ? a : b); }
这是一个函数,所以遵守作用域和访问规则。】
-
Tips:
- 对于单纯常量,最好以const对象或者enums替换
#defines
- 对于形似函数的宏(macros),最好改用inline函数替换
#defines
- 对于单纯常量,最好以const对象或者enums替换
Item03: Use const whenever possible
-
const的一些知识点:
const出现在星号左边,表示被指物是常量。星号右边则表示指针自身为常量。- 对应到迭代器:
因为迭代器其实就是像T*指针。所以声明迭代器为const就像声明指针为const一样,表示指针不能变。但是想要迭代器指向的数据不变,就得用const_iteraotr
- 对应到迭代器:
-
const面对函数声明时的应用:
- 令函数返回一个常量值
避免无意义的赋值动作
- 令函数返回一个常量值
-
const成员函数:
是为了确认该成员函数可作用于const对象身上。- 它们使class接口比较容易被理解
- 操作const对象成为可能
一个小知识点:两个成员函数如果只是常量性的不同,可以被重载!
TextBlock tb("Hello"); std::cout << tb[0]; // 调用non-const TextBlock::operator[] const TextBlock ctb("Hello"); std::cout << ctb[0]; // 调用const TextBlock::operator[]
mutable
关键字:
可以在const成员函数也可以更改成员变量!
-
在const和non-const成员函数中避免重复(常量性转移 casting away constness)
即:让non-const operator[]调用其const兄弟。可以避免代码重复!
class TextBlock { public: ...; const char& operator[](std::size_t position) const { ...; return text[position]; } char& operator[](std::size_t position) { return const_cast<char&>( static_cast<const TextBlock&>(*this) [position] ); } }
第一次转型:用
static_cast<const TextBlock&>
将*this转换。以可以调用const类型的[]
。第二次是从const operator[]
的返回类型值中移除const。- 注:不应该用const类型调用non-const类型!
-
总结:
- 将某些东西声明为const,可以帮助编译器侦测出错误用法,const可以被施加于作用域中的任何对象、函数参数、函数返回类型、成员函数本体。
- 编译器强制实施bitwise constness,但写程序的时候应该使用概念上的常量性 conceptual constness。
- 注意实现non-const和const版本的代码重复!
Item04: Make sure that objects are initialized before they're used
-
array(来自C part of C++) 不保证其内容被初始化,但是vector(来自STL part of C++)就保证初始化
-
所以建议永远都使用初始化!
- 对于构造函数,确保将每个成员初始化。
而注意,在构造函数的{}里面进行赋值,并不是初始化!而是assignment!赋值!
C++中,对象的成员变量的初始化动作发生在进入构造函数本体之前。最佳写法是:使用所谓的member initialization list。成员初始列替换赋值动作。从而构造函数本体不需要进行任何动作。
- 对于构造函数,确保将每个成员初始化。
-
成员初始化次序:
base classes总是更早于derived classes被初始化的。
而class的成员变量总是以其声明次序被初始化。 -
不同编译单元内定义之 non-local static 对象的 初始化次序:
- 注:local static对象指的是在函数里面的static对象。其他的,global对象、定义于namespace作用域内的对象、在classes内、在file作用域内被声明为static的对象称为 local static对象。
non-local static对象,会在main()结束的时候调用析构函数自动销毁。
客户机和本机的non-local static的初始化顺序很难决定,所以建议:
- 将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些寒湖是返回一个reference指向它所含的对象,然后用户调用这些函数,而不涉及对象。即non-local static对象被local static对象替换了。
这其实是Singleton模式的一个常见实现手法。可以保证获得的reference的对象指向一个历经初始化的对象。
class FileSystem { ... }; FileSystem tfs() { static FileSystem fs; return fs; } class Directory { ... }; Directory::Directory( params ) { ...; std::size_t disks = tfs().numDisks(); ...; } Directory& tempDir() { static Directory td; return td; }
-
这种函数第一行定义并初始化一个local static对象,第二行返回它。挺适合inlining的。
但是在多线程下可能有麻烦!解决方法是,在单线程启动阶段(single-threaded startup portion) 手工调用所有reference-returning 函数。
- 注:local static对象指的是在函数里面的static对象。其他的,global对象、定义于namespace作用域内的对象、在classes内、在file作用域内被声明为static的对象称为 local static对象。
-
总结:
- 为内置型对象手工初始化
- 使用成员初值列。少在构造函数里卖弄使用赋值操作。注意次序!
- 用local-static对象替代non-local static对象。
Sec2 Constructors, Destructors, and Assignment Operators
Item05: Know what functions C++ silently writes and calls
-
default 构造函数和析构函数
编译器产生的构造函数是个non-virtual。除非这个class的base class自身声明有virtual析构函数。 -
如果在一个内含reference成员的class内支持赋值操作(assignment),必须自己定义copy assignment操作符!内含const成员也一样。
或者,如果某个base classes将copyassginment操作符声明为private,编译器将拒绝为其derived classes生成一个copy assignment操作符。
-
总结:
编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符和析构函数
但是要注意特殊情况。
Item06: Explicitly disallow the use of compiler-generated functions you do not want
-
所有编译器产出的函数都是public。为组织创建,我们得自行声明它们。所以我们可以将copy构造函数或者copy assignment函数声明为private。借此可以组织自动创建。但是这也不太安全!
-
更好的做法:
class HomeForSale { public: ...; private: ...; HomeForSale(const HomeForSale&); // 只有声明 HomeForSale& operator=(const HomeForSale&); };
这里就不需要写函数参数的名称了。反正也不会用。有了这样的定义,如果客户企图拷贝对象,编译器会报错,如果member函数或friend函数这么做,连接器会报错。
这里可以将连接器错误移动到编译器,只需要将这些声明移动到private里面就行。
-
-
为了阻止HomeForSale对象被拷贝,我们也可以将它继承Uncopyable:
class HomeForSale : private Uncopyable { ...; }
这里,class就不需要声明copy构造函数或者class assign操作符了。 相对更简单一些。
-
总结:
为驳回编译器自动生成的情况,可以将相应的成员函数声明为private并且不予实现。使用像Uncopyable 这样的base class也是一种做法
Item07:Declare destructors virtual in polymorphic base classes
-
当基类有一个non-virtual析构函数的时候,且derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义 --- 实际执行的时候通常发生的是对象的derived成分没有被销毁。而derived析构函数也没有执行起来。(也就是局部销毁,这很可能形成内存泄漏、数据损坏)
-
消除这个问题的方法:给base class定义一个virtual析构函数。
class TimeKeeper { public: TimeKeeper(); virtual ~TimeKeeper(); ...; };
任何class只要带有virtual函数,几乎确定应该有一个virtual析构函数。
如果class不含virtual函数,通常不会作为base class。则建议不要将其析构函数定义为virtual(因为该类所占用的内存会发生变化,需要有虚指针和虚表)
-
例子:标准string不含有任何virtual函数,但有时候程序员会将其作为base class。这个是非常不好的!
- 因为如果程序在任意某处无意间将一个pointer-to-SpecialString转换为一个pointer-to-string。然后将转换所得的那个string指针delete,则会有行为不明确的错误
- 类似的例子适用于任何不带virtual析构函数的class。包括STL
- 总之, 不要继承一个标准容器或者任何其他带有non-virtual析构函数的class!
-
纯虚析构函数:
一般有抽象class的时候可以写!
这样就不用担心析构函数的问题了。但必须为pure virtual析构函数提供一份定义。class AWOV { public: virtual ~AWOV() = 0; }; AWOV::~AWOV() { }
-
总结:
- 多态基类应该声明一个virtual析构函数。
而且如果class带有任何的virtual函数,它就应该有一个virtual析构函数 - Class的设计目的是如果不是作为base classes使用,或者不是为了具备多态性,就不该声明virtual析构函数
- 多态基类应该声明一个virtual析构函数。
Item08: Prevent exceptions from leaving destructions
-
C++并不禁止析构函数吐出异常,但是不鼓励这样做。(比如如果有10个该析构,但是第一个就异常了,下面的9个就销毁不了了!)
-
例子:数据库连接,在析构函数中写close
class DBConnection { public: ...; static DBConnection create(); void close; }; class DBConn { // 用来管理DBConnection对象 public: ...; ~DBConn() { db.close(); } private: DBConnection db; };
-
要是close调用异常,析构函数就会传播该异常了。解决方法:DBConn的析构函数可以:
-
如果close异常就结束程序,通常通过调用abort完成
DBConn::~DBConn() { try {db.close();} catch (...) { ... 记录失败 std::abort(); } }
这样可以制止不明确行为
-
吞下因调用close而发生的异常
DBConn::~DBConn() { try {db.close();} catch (...) { ... 记录失败 } }
即:遇到错误继续执行
但这两种方法都不太好:都无法对导致close抛出异常的情况做出反应
一个较佳策略:-
重新设计DBConn接口:使其客户有机会对可能出现的问题作出反应
class DBConn { public: ... void close() { // 供客户使用 db.close(); closed = true; } ~DBConn() { if(!closed) { try {db.close();} catch (...) { ... 记录失败 } } } private: DBConnection db; bool closed; };
将调用close的责任从DBConn析构函数转移到客户手上。
-
-
-
总结:
- 析构函数绝对不要吐出异常。
如果可能异常,析构函数应该捕捉任何异常,然后吞下它们,或者结束程序。 - 如果客户需要对某个操作函数运行期间抛出的异常做出反应。那么class应该提供一个普通函数,执行该操作。
- 析构函数绝对不要吐出异常。
Item09: Never call virtual functions during construction or destruction
-
不应该在构造和析构函数中调用virtual函数。
-
因为base class的构造函数一定会在derived class之前调用,所以当会调用virtual函数的时候,这时候调用的是base class版本!
即:当derived class对象的base class构造期间,当前对象的类型是base class! -
析构函数也是类似的!
-
-
如何确保一有Transaction继承体系上的对象被创建,就会有适当版本的logTransaction被调用?
- 在class Transaction内将logTransaction函数改为non-virtual。然后要求derived class构造函数传递必要信息给Transaction构造函数。则可以安全调用logTransaction
-
总结:
在构造和析构期间不要调用virtual函数。因为这列调用从不下降至derived class。
Item10: Have assignment operators return a reference to *this
int x, y, z;
x = y = z; // 连锁赋值
为了实现连锁赋值,赋值操作符必须返回一个reference指向操作符的左侧 实参!
class Widget {
public:
...
Widget& operator=(const Widget& this) {
...
return* this; // 返回左侧对象
}
}
这个协议不仅仅适用于标准赋值形式,也适用于所有赋值相关运算。比如+=
等等。
这只是一个建议!
Item11: Handle assignment to self in operator=
在C++primer还说了挺多
-
自我赋值:
class Widget {...}; Widget w; w = w;
很蠢但是合法。
或者*px = *py;
如果两个指向同一个东西。一般而言,如果某段代码操作pointers或者references而它们被用来指向多个相同类型的对象。就需要考虑这些对象是否为同一个!实际上两个对象只要来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成别名。因为一个base class的reference或者pointer可以指向一个derived class对象。
-
如果遵循13,14的忠告,会运用对象来管理资源,而且可以确定所谓的资源管理对象在copy的时候有正确的举措,这种情况下赋值操作是自我赋值安全的(self-assignment safe)
但如果尝试自行管理资源。可能会有陷阱:在停止使用资源之前就释放了。 -
例子:
class Bitmap {...}; class Widget { ... private: Bitmap* pb; }; Widget& Widget::operator=(const Widget& rhs) { delete pb; pb = new Bitmap(*rhs.pb); return *this; }
这里的问题是,operator=函数内的*this跟rhs可能是同一个对象!这样就会导致delete错误!
-
解决问题方法:
operator=前面加一个identity test。证同测试Widget& Widget::operator=(const Widget& rhs) { if(this == &rhs) return *this; ... // 这个跟上面的一样 }
-
或者这样做:只需要注意在复制pb所指东西之前别删除pb就行了
Widget& Widget::operator=(const Widget& rhs) { Bitmap* pOrig = pb; // 记住原来的pb pb = new Bitmap(*rhs.pb); // 指向了新的复件 delete pOrig; return *this; }
如果new Bitmap抛出异常。pb会保持原状。
-
最佳方法:
用copy and swap技术:
class Widge { ... void swap(Widget& rhs); ... }; Widget& Widget::operator=(const Widget& rhs) { Widget temp(rhs); swap(temp); // 交换 return *this; }
利用了事实:
- 某个class 的copy assignment操作符可能被声明为by value传参。
- by value传参会造成一份副本。
个人想法:
感觉可能直接在swap中实现证同测试,这样直接swap就行了。上面想法是错误的!因为这样rhs就不会删除了!而temp可以在离开{}后,自我销毁!
-
-
总结:
- 确保当对象自我复制的时候operator=有良好的行为。
- 确定任何函数如果操作一个以上的对象,而其中对个对象是同一个对象的时候,行为仍然正确。
Item12: Copy all parts of an object
-
设计良好的面向对象系统会将对象的内部封装起来,只留两个函数负责对象拷贝(赋值)。即copy构造函数和copy assignment 操作符。我们将其称为copying函数。
-
注意:如果我们自己写了拷贝构造或者拷贝赋值函数,如果我们加了成员变量,我们也需要在这两个函数中加上初始化操作。
-
注意:如果我们还写了derived class,我们也需要注意我们编写derived class的构造函数或者赋值函数的时候也要初始化从base class那里继承来的所有值!
也就是:主要调用base class的拷贝构造函数!PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) : Customer(rhs), priority(rhs.priority) { logCall(); } PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs) { logCall(); Customer::operator=(rhs); // 对base class成分进行赋值操作 priority = rhs.priority; return *this; }
-
故:复制每一个成分指的是:
当编写一个copying 函数,请确保:
- 复制所有local成员变量
- 调用所有base classes内的适当copying函数、
-
注意:不该令copy assignment操作符调用copy 构造函数!
也不该让copy构造函数调用copy assignment操作符
消除代码重复的方法:建议是在private里面定义一个init成员函数给两者使用!
Sec03: Resource Management
- 所谓资源:用了之后需要返还给系统
- 资源:内存、文件描述其、互斥锁、图形界面中的字型和笔刷、数据库连接、网络sockets
Item13: Use objects to manage resource
-
若只是这样:
void f() { Investment* pInv = createInvestment(); ... delete pInv; }
如果...中报错或者返回,会产生灾难性后果,所以解决方法是把资源放进对象内,当控制流离开f,该对象的析构函数会自动释放那些资源。(C++的析构函数自动调用机制)
-
auto_ptr
: 类指针 (pointer-like) 对象。也就是所谓的智能指针。
其析构函数自动对其所指对象调用delete。这个就是
unique_ptr
等智能指针的鼻祖 -
两个关键思想
-
获得资源后立刻放进管理对象(managing object)内
以对象管理资源的观念常常被称为“资源取得时机便是初始化时机” (Resource Acquisition Is Initialization (RAII))
-
管理对象运用析构函数确保资源被释放
所以注意别让auto_ptr同时指向同一对象(所以使用unique_ptr和shared_ptr)
所以auto_ptr的一个特性,当通过复制构造函数或者复制赋值操作符复制他们的时候,原来的会变成nullptr!然后后面的会指向原来的资源
-
-
替代思想:
- 引用计数型智能指针:reference-counting smart-pointer: RCSP
shared_ptr
- 缺点:无法打破环状互指:即两个已经没有使用的对象在彼此互指,因为好像还在使用的状态
- 引用计数型智能指针:reference-counting smart-pointer: RCSP
-
注意:
auto_ptr和shared_ptr两者在析构函数内做delete而不是delete []
. 那意味着动态分配而得的array身上使用auto_ptr或tr1::shared_ptr是不明智的。但是这是可以通过编译的。std::auto_ptr<std::string> aps(new std::string[10]); std::shared_ptr<int> spi(new int[1024]);
因为vector和string几乎总是可以取代动态分配而得到的数组!
-
总结:
- 使用RAII对象,在构造函数获取资源,析构函数释放资源
- shared_ptr
Item14: Think carefully about copying behavior in resource-managing class
-
并非所有资源都是heap-based。对那种资源来说,智能指针就不太适合了,可能需要我们建立自己的资源管理类。
-
例子:
使用C api函数处理类型为Mutex的互斥器对象。有lock和unlock两个函数可以用void lock(Mutex* pm); void unlock(Mutex* pm);
为确定不会忘记解锁,我们可以建立一个class用来管理锁。由RAII守则支配。
class Lock { public: explicit Lock(Mutex* pm) : mutexPtr(pm) { lock(mutexPtr); } ~Lock() { unlock(mutexPtr); } private: Mutex* mutexPtr; }
-
如果Lock对象被复制,会发生什么事情?(也就是当一个RAII对象被复制,会发生什么事情?)
解决情况:
-
禁止复制
参考item6:private继承其Uncopyable类就行了(就是把拷贝赋值和拷贝构造定义到了private里面)class Lock : private Uncopyable { public: ... }
-
对底层资源使用“引用计数法” reference count。比如shared_ptr
RAII classes便可以是先出reference-counting copying的行为。-
进一步:用shared_ptr实现Lock
因为shared_ptr允许指定所谓的删除器(deleter - 一个函数或者函数对象)
删除器将在引用为0的时候被调用。class Lock { public: explicit Lock(Mutex* pm) : mutexPtr(pm, unlock) // 指定unlock! { lock(mutexPtr.get()); // get就是得到原始指针 } private: std::tr1::shared_ptr<Mutex> mutexPtr; }
此次没必要声明Lock class的析构函数,因为没有必要。因为item5说过,class析构函数会自动调用其non-static成员变量的析构函数!然后mutexPtr会在引用为0的时候自动调用删除器!
-
-
复制底部资源
复制资源管理对象的时候,进行的是深度拷贝!也就是要复制其所包覆的资源! -
转移底部资源的拥有权:
即auto_ptr
复制的时候原来的变成nullptr,转移指针!
-
-
总结:
- 复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定了RAII对象的copying行为
- 普遍常见的RAII copying做法是禁止复制和引用计数。
Item15: Provide access to raw resources in resource-managing classes
-
有的API直接需要raw resource。所以建议提供一个能访raw resource的方法
-
例子:
std::shared_ptr<Investment> pInv(createInvestment()); // 然后有一个函数 int daysHeld(const Investment* pi); // 这样调用是错误的! int days = daysHeld(pInv);
-
解决方法:
-
显示转换
shared_ptr和auto_ptr都提供一个get成员函数,用来执行显示转换为其原始指针(的复件)int days = daysHeld(pInv.get());
-
隐式转换
比如shared_ptr等智能指针可以使用->和.运算符 -
提供隐式转换函数
调用get有泄露危险! -> 提供隐式转换函数
见书上的Font例子。class Font { public: ...; operator FontHandle() const { return f; } ...; }
但可能会增加错误机会!
-
-
具体使用哪个解决方法要看具体情况,当然最好还是用item18的让接口容易使用,不易被误用
-
一般来说get()是比较好的选择
-
总结:
- APIs往往要求访问raw resources,所以每一个RAII class应该提供一个取得源数据的方法
- 显示转换更安全,但隐式转换对客户方便
Item16: Use the same form in corresponding uses of new and delete
- 就是删除数组要用delete [] xxx;
而且要特别注意typedef定义的是不是一个数组!所以最好不要将数组typedef!
Item17: Store newed objects in smart pointers in standalone statements
-
使用对象管理式资源,也可能泄露资源
processWidget(std::shared_ptr<Widget>(new Widget), priority());
这些事情的执行顺序可能不太一样!特别是对priority()的调用!
万一priority()调用异常,new Widget返回的指针将会遗失! -
避免此类问题的方法:
分离语句std::shared_ptr<Widget> pw(new Widget); processWidget(pw, priority());