Effective C++
ba_jie
这个作者很懒,什么都没留下…
展开
-
Item 6 如何禁用编译器产生的函数
如果要禁用编译器自动产生的copy ctor和拷贝构造函数,一般用下面的方法:class UniqueMoo {public: ...private: UniqueMoo(const UniqueMoo&); // 1. 只声明不定义,而且声明为私有,即可 UniqueMoo& operator = (const UniqueMoo&); // 1. 防止客户调用。用则产生编译错。若用};翻译 2011-05-08 12:25:00 · 394 阅读 · 0 评论 -
Item 30 内联函数
内联函数像函数,但是却省下了调用函数的时空开销。但实际上,编译器做的不止于此。因为编译器的优化一般是针对一整块没有函数调用的代码,所以,如果你使用了内联函数,编译器也许会根据上下文再优化一下内联函数的函数体。使用内联函数的代价:会使目标文件变大;由此也更耗内存;太多的内联函数导致内存分页过多;最后因频繁的页面交换降低CPU的缓存利用。但是,当内联函数非常短的时候,目标文件反而变小;最后反而提高了CPU的缓存利用率。关键字inline只是对编译器的一个请求,而非命令。编译器有可能不理会这个请求。+翻译 2011-04-21 10:31:00 · 844 阅读 · 0 评论 -
Item 8 防止因异常而离开析构函数
C++在语法上并不禁止从dtor中引发异常。但这在实践中会构成隐患。如下面:class Widget {public: ... ~Widget() { ... } // assume this might emit an exception};void doSomething(){ std::vector v; ...} // v is automatically destroyed here在某种条件下同时产生两个异常,会导致程序的提前中止,或者引发未翻译 2011-04-21 10:16:00 · 721 阅读 · 0 评论 -
Simple Items
● Item 7 多态基类中将析构函数声明为虚拟1> 多态基类应该声明虚析构函数。如果一个类有任何虚函数,它就应该有一个虚析构函数。2> 纯虚析构函数也必须有函数体。否则linker报错。● Item 21 必须返回对象时别返回引用1> 绝不要返回一个局部栈对象的指针或引用2> 绝不要返回一个被分配的堆对象的引用。原因是caller在使用该引用时,可能会因为无法保存引用而导致资源泄漏。3> 如果存在需要一个以上这样的对象的可能性时,绝不要返回一个局部 static 对象的指针或引用。原因:多个地方使用同一个翻译 2011-04-21 09:53:00 · 729 阅读 · 0 评论 -
Item 31 减小文件之间的编译依赖
C++在接口和实现之间的分离这部分工作做的不好。像下面这样的类定义,不仅指明了接口,也包含了一部分的实现细节。这将导致对实现的依赖。实现一旦改变,将增加整个的编译时间。class Person{public: Person(const std::string& name, const Date& birthday, const Address& addr); std::string name() const; std::string birthDate() const; std::s翻译 2011-04-21 10:42:00 · 558 阅读 · 0 评论 -
Item 17 将new出来的对象存入智能指针
现在有两个函数是这样使用的:// 取得处理优先级int priority();// 根据优先级,对动态分配的Widget做一些处理void processWidget(std::tr1::shared_ptr pw, int priority);如果使用下面的方法调用函数,可能会发生资源泄漏:processWidget(std::tr1::shared_ptr(new Widget), priority());原因是:函数的参数执行顺序是不一定的。如果new出来对象以后,还没有存入智能指针,这时翻译 2011-04-21 10:23:00 · 573 阅读 · 0 评论 -
Item 27 强制类型转换
● C++中可使用两种旧式风格的类型转换:1> (T) expression C风格2> T(expression) 函数风格● C++也提供了新式风格的类型转换:1> const_cast(expression) 去掉对象的常量属性2> dynamic_cast(expression) 向下安全的类型转换3> reinterpret_cast(expression) 低级操作,因为依赖实现所以无法移植4> static_cast(e翻译 2011-04-21 10:59:00 · 540 阅读 · 0 评论 -
Item 43 访问模板化基类中的名字
假设我们要写一个应用程序:1> 它可以把消息传送到几个不同的公司去,且每个公司有自己的消息发送机制;2> 消息既可以以加密方式也可以以明文的方式传送;3> 有足够的信息在编译期间就可确定哪个消息将要发送给哪个公司那么,可以用一个模板来解决问题。class CompanyA // 公司A{public: ... void sendCleartext(const std::string& msg); void sendEncrypted(const std::st翻译 2011-04-21 10:49:00 · 443 阅读 · 0 评论 -
Item 51 new和delete的规则
<br />● 全局new/delete重载<br />void * operator new(std::size_t size) throw(std::bad_alloc){ using namespace std; if (size == 0) { // C++规定:零申请也要给一个字节 size = 1; } void* mem = NULL; while (true) { mem = malloc(size); if (mem) { ret翻译 2011-05-31 14:01:00 · 595 阅读 · 0 评论 -
Item 9 避免在ctor和dtor中调用虚函数
看下面的程序有什么问题。class Transaction {public: Transaction(); virtual void logTransaction() const = 0; // 实际的日志记录在派生类里实现,这将根据不同的类型生成不同的日志。(我们本意是如此) ...};Transaction::Transaction(){ ... logTransaction(); // 但是,此处会调用派生类的实现吗?}class BuyTran翻译 2011-04-21 10:02:00 · 724 阅读 · 0 评论 -
Item 13 管理在堆上new出来的对象
void f(){ std::auto_ptr pInv(createInvestment()); ...}创建资源之后立即交给资源管理器,这叫Resource Acquisition Is Initialization (RAII)。资源管理器在dtor里销毁资源。如果发生异常,会很麻烦。不要用多个auto_ptr管理同一个资源。否则会发生多次销毁。auto_ptr本身也改装了copy动作(copy ctor和assignment operator)。std::auto_ptr p翻译 2011-04-25 13:26:00 · 448 阅读 · 0 评论 -
Item 42 typename的使用
template class Widget;template class Widget;这两种声明区别不大。但在一些场合,必须使用typename。template // typename/class都行void f(const C& container, // 直接用C就好 typename C::iterator iter); // 必须加typename在C前面iterator是定义在C内部的类型。这样的类翻译 2011-05-09 14:51:00 · 1519 阅读 · 0 评论 -
Item 26 推迟变量的定义
对于有ctor和dtor的变量,如果定义得过早,就有可能带来不必要的构造和析构,从而带来性能上的损失。std::string encryptPassword(const std::string& password){ using namespace std; string encrypted; if (password.length() 如果中途出现异常而退出,则encrypted没有使用到,白白调用了ctor和dtor。翻译 2011-05-09 00:01:00 · 914 阅读 · 0 评论 -
Item 36 非虚拟的成员函数不能重定义
class B {public: void mf(); ...};class D: public B { ... void mf(); // 1. 重定义基类中的non-virtual成员函数};D x;B *pB = &x;D *pD = &x;pB->mf(); // 调用的都是x的mf函数,若结果不一样,你是否pD->mf(); // 会奇怪?其实没什么,这类函数都是静态绑定虚函数才是动态绑定,在运行时根据指针指向的对象的实际类型,决定调用的函翻译 2011-05-08 13:23:00 · 915 阅读 · 0 评论 -
Item 39 私有继承
<br />C++编译器把公有继承的类当作基类的特例,私有继承则不同:<br /> <br />class Person { ... };class Student: private Person { ... }; // 私有继承void eat(const Person& p); // Person可以“吃”void study(const Student& s); // Student可以“学”Person p;翻译 2011-04-30 14:03:00 · 796 阅读 · 0 评论 -
Item 34 分清对接口的继承和对实现的继承
需求:1> 通过派生,只继承基类的函数接口(即它们的声明);2> 接口和实现都继承,而且不让它们被派生类覆盖;3> 接口和实现都继承,允许它们被派生类覆盖。class Shape { // an abstract classpublic: virtual void draw() const = 0; // 纯虚函数:只继承接口 virtual void error(const std::string& msg); // 简单虚函数(有实现的虚函数;非纯虚函数):翻译 2011-04-24 12:35:00 · 434 阅读 · 0 评论 -
Item 40 多重继承
1> 有多重继承,就有可能继承相同的名字:class BorrowableItem {public: void checkOut(); ...};class ElectronicGadget {private: bool checkOut() const; ...};class MP3Player : public BorrowableItem , public ElectronicGadget{ ... }; // 定义不重要MP3Player mp翻译 2011-04-25 17:53:00 · 565 阅读 · 0 评论 -
Item 15 访问资源管理类内的资源
class Investment {public: bool isTaxFree() const; ...};// return number of days investment has been held// 需要Investment作为参数int daysHeld(const Investment *pi);// 工厂函数Investment* createInvestment();// 使用shared_ptr管理Investment类的对象std::tr翻译 2011-04-25 15:27:00 · 589 阅读 · 0 评论 -
Item 14 资源管理类的拷贝
RAII对象只能管理堆上new出来的资源,其它的不行。比如下面的mutex:void lock(Mutex *pm); // lock mutex pointed to by pmvoid unlock(Mutex *pm); // unlock the mutex只能自定义一个资源管理类,来管理这个Mutex资源,以防止它被意外地忽略。class Lock {public: explicit Lock(Mutex *pm) : mutexPt翻译 2011-04-25 13:33:00 · 471 阅读 · 0 评论 -
Item 52 new/delete成对儿出现
<br />● 通用概念<br />Widget *pw = new Widget;<br />其实分为两步:<br />1> 调用operator new分配内存<br />2> 调用ctor初始化对象<br /><br />如果第2步抛出异常,系统会自动调用<br />1> void operator delete(void *mem) throw();<br />2> void Widget::operator delete(void *mem, std::size_t s翻译 2011-05-31 13:26:00 · 614 阅读 · 0 评论 -
Item 29 异常安全
<br />● 所谓“异常安全”,指的是当抛出异常时,要做到:<br />1> 资源无泄漏。申请的资源都释放掉。<br />2> 数据无破坏。数据都处于合法状态,不会出现无法解释的数据。<br /><br />满足这两点,就做到了基本的“异常安全”。<br />如果能在异常发生时把程序返回到调用前的状态,那就做到了高级的“异常安全”。<br />如果能保证绝不抛出异常,那就是完美的“异常安全”。<br /><br />● 如果某函数的异常列表是空的,表明它一旦出现异常,将是极严重的错翻译 2011-05-28 18:45:00 · 725 阅读 · 0 评论 -
Item 12 注意copy ctor和赋值函数
如果一个类有自定义的copy ctor和赋值函数,那么:● 在向其添加成员变量的时候,也要向其所有的ctor和赋值函数添加变量的初始化● 派生的时候,也要调用基类相关的函数:class PriorityCustomer: public Customer {public: ... PriorityCustomer(const PriorityCustomer& rhs); PriorityCustomer& operator=(const PriorityCustomer& rhs);翻译 2011-05-17 12:04:00 · 692 阅读 · 0 评论 -
Item 49 new_handler
内存分配失败时,老的C++编译器返回null空指针;新的则抛出bad_alloc异常。在此之前,operator new要先调用一个函数。此函数可由用户定制,叫做new_handler。由该函数处理内存不足这个问题。直到找到合适的内存为止,将反复调用该函数。该函数由std::set_new_handler注册给operator new:namespace std { typedef void (*new_handler)(); new_handler set_new_handler(ne翻译 2011-05-17 10:30:00 · 530 阅读 · 0 评论 -
Item 48 TMP编程介绍
Item 47介绍了一个advance的例子。如果用typeid的方式,会出现两个问题。其中之一如下:templatevoid advance(IterT& iter, DistT d){ if (typeid(typename std::iterator_traits::iterator_category) == typeid(std::random_access_iterator_tag)) { iter += d; // compile error } ...翻译 2011-05-16 15:46:00 · 884 阅读 · 0 评论 -
Item 47 用trait类提供类型信息
STL主要由容器、迭代器和算法这三类模板组成。也包含一些工具模板,比如:templatevoid advance(IterT& it, DistT d); // it += d在实现上,只有支持随机访问的迭代器才能用+=,功能稍弱的只能用++和--重复执行。迭代器分5类:1> Input Iterator 只能前行,一次一步,指谁读谁,而且只能读一次。代表:istream_iterator2> Output Iterator 与上同。用于写操作。代表:ostrea翻译 2011-05-16 14:36:00 · 679 阅读 · 0 评论 -
Item 46 模板需要类型转换的时候
把Item 24的例子改成模板:templateclass Rational {public: Rational(const T& numerator = 0, const T& denominator = 1); const T numerator() const; const T denominator() const; ...};templateconst Rational operator*(const Rational& lhs,翻译 2011-05-14 17:11:00 · 588 阅读 · 0 评论 -
Item 45 成员函数模板
<br />STL容器使用的iterator几乎都是智能指针,所以才能以++操作在节点之间移动。<br />但真正的指针,支持隐式类型转换:<br />1> 指向“派生类对象”的指针可以转成指向“基类对象”的指针;<br />2> 指向“non-const对象”的指针可以转成指向“const对象”的指针;<br /><br />智能指针做不到这些。<br /> <br />template<typename T>class SmartPtr {public: explicit S翻译 2011-05-13 10:51:00 · 477 阅读 · 0 评论 -
Item 44 与模板参数无关的代码
模板可以节省时间,减少代码的重复。假设有20个类,每个类15个成员函数。那么可以用一个类模板来定义,然后由编译器去实例化所需要的类。模板类的成员函数只在用到的时候才实例化。过度使用模板会导致目标代码的膨胀。要通过共性/异性分析防止这种事情发生。// n*n矩阵,元素类型为Ttemplateclass SquareMatrix {public: ... void invert(); // 求逆矩阵};SquareMatrix sm1;sm1.invert();翻译 2011-05-12 11:01:00 · 611 阅读 · 0 评论 -
Item 25 不应抛出异常的swap
通用的swap可以在stl里找到:namespace std { template // 1. std::swap的实现 void swap(T& a, T& b) { T temp(a); a = b; // 2. 只要T支持拷贝 b = temp; }}有很多类使用pimpl来分离实现与接口,对它们的swap要慎重。class WidgetImpl { // 真正的数据与函数实现放在这个类翻译 2011-05-08 19:00:00 · 647 阅读 · 0 评论 -
Item 11 赋值函数的自赋值
class Widget { ... };Widget w;...w = w; // 1. 能通过编译,但是明显不合理!...a[i] = a[j]; // 2. ij可能相同*px = *py; // 3. 二者可能相同或有继承关系上面的三种情况,都是给自己赋值,要在赋值时检查。class Bitmap { ... };class Widget { ...private: Bitmap *pb;};// 既无赋值安全性,又无异常安全性的翻译 2011-05-20 12:48:00 · 716 阅读 · 0 评论 -
Item 35 考虑虚函数的替代者
<br />● NVI: Non-Virtual Interface<br />class GameCharacter {public: int healthValue() const // 1. 子类不能重定义 { ... // 2. preprocess int retVal = doHealthValue(); // 2. 真正的工作放到虚函数中 ...翻译 2011-05-28 16:03:00 · 622 阅读 · 0 评论 -
Item 5 编译器自动生成和调用的函数
● 一个空类,编译器会在其中加入四个public的内联函数:1> default ctor2> copy ctor 和一个赋值函数:负责处理non-static成员变量3> 一个非虚拟的dtor;如果该类派生自一个有虚拟dtor的基类,结论相反这四个函数会在被调用的时候构造出来。● 下面三种情况,编译器不会自动生成赋值函数,会报错。templateclass NamedObject {public: NamedObject(const char *name, const T&翻译 2011-05-28 15:54:00 · 709 阅读 · 0 评论 -
Item 50 重载new和delete
<br />重载new和delete的目的:<br />● 监视内存的使用。<br />1> new和delete没有一对一。new多了内存泄漏;delete多了导致未定义行为。此时需要一个带log的new。<br />2> overrun and underrun。向内存写数据时,写到了缓冲区的外面。此时需要new能申请稍大一点的空间存储标志位。<br />● 提高内存分配的效率<br />● 统计内存使用的规律<br /><br />下面是一个检测overrun/underrun翻译 2011-05-29 18:11:00 · 598 阅读 · 0 评论 -
Item 33 派生时小心重名函数族
<br />1> 普通变量的名字覆盖<br />int x;void someFunc(){ double x; // 虽然是double,但对编译器而言,还是和 std::cin >> x; // 上面的int重名,此处只能访问double的} <br /> <br />2> 派生时发生的名字覆盖<br />class Base {private: int x;public: virtual void mf1() = 0; virtual void m翻译 2011-05-28 17:07:00 · 611 阅读 · 0 评论 -
Item 37 子类不能修改成员函数的默认参数值
<br />class Shape {public: enum ShapeColor { Red, Green, Blue }; virtual void draw(ShapeColor color = Red) const = 0; // 默认是红 ...};class Rectangle: public Shape {public: virtual void draw(ShapeColor color = Green) const; // 默认改为绿 ...}翻译 2011-05-28 16:04:00 · 773 阅读 · 0 评论 -
Item 20 传引用的好处
<br />1> 不仅效率高,而且避免了“对象切割”的问题:<br />class Window {public: ... std::string name() const; virtual void display() const; };class WindowWithScrollBars: public Window {public: ... virtual void display() const;};void printNameAndDisp翻译 2011-05-28 15:58:00 · 722 阅读 · 0 评论 -
Item 19 类的设计
设计一个类,要考虑如下几方面:1> 如何创建、销毁?这会影响:ctor, dtor, operator new, operator delete2> 初始化和赋值有什么差别?3> 传值的时候要做什么?会影响copy ctor4> 合法值都有哪些?会影响ctor、赋值、setter、异常。5> 是否继承自某个类体系?如果是,要遵循其定义规范。6> 需要什么样的类型转换?是隐式的还是显示的?7> 操作符8> 编译器生成的函数是否有不需要的9> 谁可以访问其成员10> 如果翻译 2011-05-28 15:57:00 · 642 阅读 · 0 评论 -
Item 23 多考虑“非成员、非友元”函数
<br />class WebBrowser {public: ... void clearCache(); void clearHistory(); void removeCookies(); ...};<br /> <br />为了提供一站式服务,定义一个“清除一切”的函数。下面是两种方案:<br /><br />1> 成员函数<br />class WebBrowser {public: ... void clearEverything(); ...翻译 2011-05-28 16:01:00 · 723 阅读 · 0 评论 -
Item 18 不易误用的接口
● 用类型限制用户class Date {public: Date(int month, int day, int year); ...};Date d(30, 3, 1995); // 日、月用反了Date d(2, 30, 1995); // 日、月不匹配重新设计接口:struct Day { explicit Day(int d) :val(d) {} int val;};struct Month { explicit Month(int m) :v翻译 2011-05-28 15:56:00 · 611 阅读 · 0 评论 -
Item 41 隐式接口和编译时多态
OOP编程与类属编程(generic programming)的一大区别:1> OOP使用显式的接口和运行时多态2> 类属编程使用隐式接口和编译时多态void doProcessing(Widget& w){ if (w.size() > 10 && w != someNastyWidget) { Widget temp(w); temp.normalize(); temp.swap(w); }}当你看到上面的代码时,你能在某个文件里找到下面的声明:class Wi翻译 2011-05-09 10:49:00 · 599 阅读 · 0 评论