RSS 2.0 Feed
More Effective C++
翻译 : zhc http://blog.csdn.net/zhc/
摘要:本文含有图片,无法贴上,请下载WORD文档阅读。下载 有时你想这样管理某些对象,要让某种类型的对象能够自我销毁,也就是能够“delete this.” 很明显这种管理方式需要此类型对象要被分配在堆中。而其它一些时候你想获得一种保障:“不在堆中分配对象,从而保证某种类型的类不会发生内存泄漏。”如果你在嵌入式系统上工作,就有可能遇到这种情况,发生在嵌入式系统上的内存泄漏是极其严重的,其堆空间是非常珍贵的。有没有可能编写出代码来要求或禁止在堆中产生对象(heap-based object)呢?通常是可以的,不过这种代码也会把“on the heap”的概念搞得比你脑海中所想的要模糊。 要求在堆中建立对象 让我们先从必须在堆中建立对象开始说起。为了执行这种限制,你必须找到一种方法禁止以调用“new”以外的其它手段建立对象。这很容易做到。非堆对象(non-heap object)在定义它的地方被自动构造,在生存时间结束时自动被释放,所以只要禁止使用隐式的构造函数和析构函数,就可以实现这种限制。 把这些调用变得不合法的一种最直接的方法是把构造函数和析构函数声明为private。这样做副作用太大。没有理由让这两个函数都是private。最好让析构函数成为private,让构造函数成为public。处理过程与条款26相似,你可以引进一个专用的伪析构函数,用来访问真正的析构函数。客户端调用伪析构函数释放他们建立的对象。 例如,如果我们想仅仅在堆中建立代表unlimited precision numbers(无限精确度数字)的对象,可以这样做: class UPNumber { public:   UPNumber();   UPNumber(int initValue);   UPNumber(double initValue);   UPNumber(const UPNumber& rhs);     // 伪析构函数 (一个const 成员函数, 因为   // 即使是const对象也能被释放。)   void destroy() const { delete this; }     ...   private:   ~UPNumber(); }; 然后客户端这样进行程序设计: UPNumber n;                          // 错误! (在这里合法,但是                                      // 当它的析构函数被隐式地                                      // 调用时,就不合法了)   UPNumber *p = new UPNumber;          //正确   ...   delete p;                            // 错误! 试图调用                                      // private 析构函数   p->destroy();                        // 正确 另一种方法是把全部的构造函数都声明为private。这种方法的缺点是一个类经常有许多构造函数,类的作者必须记住把它们都声明为private。否则如果这些函数就会由编译器生成,构造函数包括拷贝构造函数,也包括缺省构造函数;编译器生成的函数总是public(参见Effecitve C++ 条款45)。因此仅仅声明析构函数为private是很简单的,因为每个类只有一个析构函数。 通过限制访问一个类的析构函数或它的构造函数来阻止建立非堆对象,但是在条款26已经说过,这种方法也禁止了继承和包容(containment): class UPNumber { ... };              // 声明析构函数或构造函数                                      // 为private   class NonNegativeUPNumber:   public UPNumber { ... };           // 错误! 析构函数或                                      //构造函数不能编译   class Asset { private:   UPNumber value;   ...                                // 错误! 析构函数或                                      //构造函数不能编译 }; 这些困难不是不能克服的。通过把UPNumber的析构函数声明为protected(同时它的构造函数还保持public)就可以解决继承的问题,需要包含UPNumber对象的类可以修改为包含指向UPNumber的指针: class UPNumber { ... };              // 声明析构函数为protected   class NonNegativeUPNumber:  ......[阅读全文]

posted @ | Feedback (0) |

摘要:到目前为止,这种逻辑很正确,但是不够深入。最根本的问题是对象可以被分配在三个地方,而不是两个。是的,栈和堆能够容纳对象,但是我们忘了静态对象。静态对象是那些在程序运行时仅能初始化一次的对象。静态对象不仅仅包括显示地声明为static的对象,也包括在全局和命名空间里的对象(参见条款47)。这些对象肯定位于某些地方,而这些地方既不是栈也不是堆。 它们的位置是依据系统而定的,但是在很多栈和堆相向扩展的系统里,它们位于堆的底端。先前内存管理的图片到讲述的是事实,不过是很多系统都具有的事实,但是没有告诉我们这些系统全部的事实,加上静态变量后,这幅图片如下所示: onHeap不能工作的原因立刻变得很清楚了,不能辨别堆对象与静态对象的区别: void allocateSomeObjects() {   char *pc = new char;               // 堆对象: onHeap(pc)                                      // 将返回true     char c;                            // 栈对象: onHeap(&c)                                      // 将返回false     static char sc;                    // 静态对象: onHeap(&sc)                                      // 将返回true   ...   } 现在你可能不顾一切地寻找区分堆对象与栈对象的方法,在走头无路时你想在可移植性上打主意,但是你会这么孤注一掷地进行一个不能获得正确结果的交易么?绝对不会。我知道你会拒绝使用这种虽然诱人但是不可靠的“地址比对”技巧。 令人伤心的是不仅没有一种可移植的方法来判断对象是否在堆上,而且连能在多数时间正常工作的“准可移植”的方法也没有。如果你实在非得必须判断一个地址是否在堆上,你必须使用完全不可移植的方法,其实现依赖于系统调用,只能这样做了。因此你最好重新设计你的软件,以便你可以不需要判断对象是否在堆中。 如果你发现自己实在为对象是否在堆中这个问题所困扰,一个可能的原因是你想知道对象是否能在其上安全调用delete。这种删除经常采用“delete this”这种声明狼籍的形式。不过知道“是否能安全删除一个指针”与“只简单地知道一个指针是否指向堆中的事物”不一样,因为不是所有在堆中的事物都能被安全地delete。再考虑包含UPNumber对象的Asset对象: class Asset { private:   UPNumber value;   ...   };   Asset *pa = new Asset; 很明显*pa(包括它的成员value)在堆上。同样很明显在指向pa->value上调用delete是不安全的,因为该指针不是被new返回的。 幸运的是“判断是否能够删除一个指针”比“判断一个指针指向的事物是否在堆上”要容易。因为对于前者我们只需要一个operator new返回的地址集合。因为我们能自己编写operator new函数(参见Effective C++条款8—条款10),所以构建这样一个集合很容易。如下所示,我们这样解决这个问题: void *operator new(size_t size) {   void *p = getMemory(size);         //调用一些函数来分配内存,                                      //处理内存不够的情况     把 p加入到一个被分配地址的集合;     return p;   }   void operator delete(void *ptr) {   releaseMemory(ptr);                // return memory to                                      // free store     从被分配地址的集合中移去ptr; }   bool isSafeToDelete(const void *address) {   返回address是否在被分配地址的集合中; } 这很简单,operator new在地址分配集合里加入一个元素,operator delete从集合中移去项目,isSafeToDelete在集合中查找并确定某个地址是否在集合中。如果operator new 和 operator delete函数在全局作用域中,它就能适用于所有的类型,甚至是内建类型。 在实际当中,有三种因素制约着对这种设计方式的使用。第一是我们极不愿意在全局域定义任何东西,特别是那些已经具有某种含义的函数,象operator new和operator......[阅读全文]

posted @ | Feedback (0) |

摘要:灵巧指针是一种外观和行为都被设计成与内建指针相类似的对象,不过它能提供更多的功能。它们有许多应用的领域,包括资源管理(参见条款9、10、25和31)和重复代码任务的自动化(参见条款17和29) 当你使用灵巧指针替代C++的内建指针(也就是dumb pointer),你就能控制下面这些方面的指针的行为: 构造和析构。你可以决定建立灵巧指针时应该怎么做。通常赋给灵巧指针缺省值0,避免出现令人头疼的未初始化的指针。当指向某一对象的最后一个灵巧指针被释放时,一些灵巧指针负责删除它们指向的对象。这样做对防止资源泄漏很有帮助。 拷贝和赋值。你能对拷贝灵巧指针或设计灵巧指针的赋值操作进行控制。对于一些类型的灵巧指针来说,期望的行为是自动拷贝它们所指向的对象或用对这些对象进行赋值操作,也就是进行deep copy(深层拷贝)。对于其它的一些灵巧指针来说,仅仅拷贝指针本身或对指针进行赋值操作。还有一部分类型的灵巧指针根本就不允许这些操作。无论你认为应该如何去做,灵巧指针始终受你的控制。 Dereferencing(取出指针所指东西的内容)。当客户端引用被灵巧指针所指的对象,会发生什么事情呢?你可以自行决定。例如你可以用灵巧指针实现条款17提到的lazy fetching 方法。 灵巧指针从模板中生成,因为要与内建指针类似,必须是strongly typed(强类型)的;模板参数确定指向对象的类型。大多数灵巧指针模板看起来都象这样: template<class T>                    //灵巧指针对象模板 class SmartPtr {                     public:   SmartPtr(T* realPtr = 0);          // 建立一个灵巧指针                                      // 指向dumb pointer所指的                                      // 对象。未初始化的指针                                      // 缺省值为0(null)     SmartPtr(const SmartPtr& rhs);     // 拷贝一个灵巧指针     ~SmartPtr();                       // 释放灵巧指针     // make an assignment to a smart ptr   SmartPtr& operator=(const SmartPtr& rhs);     T* operator->() const;             // dereference一个灵巧指针                                      // 以访问所指对象的成员     T& operator*() const;              // dereference 灵巧指针   private:   T *pointee;                        // 灵巧指针所指的对象 };                                  拷贝构造函数和赋值操作符都被展现在这里。对于灵巧指针类来说,不能允许进行拷贝和赋值操作,它们应该被声明为private(参见Effective C++条款27)。两个dereference操作符被声明为const,是因为dereference一个指针时不能对指针进行修改(尽管可以修改指针所指的对象)。最后,每个指向T对象的灵巧指针包含一个指向T的dumb pointer。这个dumb pointer指向的对象才是灵巧指针指向的真正对象。 进入灵巧指针实作的细节之前,应该研究一下客户端如何使用灵巧指针。考虑一下,存在一个分布式系统(即其上的对象一些在本地,一些在远程)。相对于访问远程对象,访问本地对象通常总是又简单而且速度又快,因为远程访问需要远程过程调用(RPC),或其它一些联系远距离计算机的方法。 对于编写程序代码的客户端来说,采用不同的方法分别处理本地对象与远程对象是一件很烦人的事情。让所有的对象都位于一个地方会更方便。灵巧指针可以让程序库实现这样的梦想。 template<class T>                    // 指向位于分布式 DB(数据库) class DBPtr {                        // 中对象的灵巧指针模板 public:                              //     DBPtr(T *realPtr = 0);             // 建立灵巧指针,指向                                      // 由一个本地dumb pointer                                      // 给出的DB 对象       DBPtr(DataBaseID id);              // 建立灵巧指针,                                      //......[阅读全文]

posted @ | Feedback (0) |

摘要: 测试灵巧指针是否为NULL 目前为止我们讨论的函数能让我们建立、释放、拷贝、赋值、dereference灵巧指针。但是有一件我们做不到的事情是“发现灵巧指针为NULL”: SmartPtr<TreeNode> ptn;   ...   if (ptn == 0) ...                    // error!   if (ptn) ...                         // error!   if (!ptn) ...                        // error! 这是一个严重的限制。 在灵巧指针类里加入一个isNull成员函数是一件很容易的事,但是仍然没有解决当测试NULL时灵巧指针的行为与dumb pointer不相似的问题。另一种方法是提供隐式类型转换操作符,允许编译上述的测试。一般应用于这种目的的类型转换是void* : template<class T> class SmartPtr { public:   ...   operator void*();                  // 如果灵巧指针为null,   ...                                // 返回0,否则返回 };                                   // 非0。   SmartPtr<TreeNode> ptn;   ...   if (ptn == 0) ...                    // 现在正确   if (ptn) ...                         // 也正确   if (!ptn) ...                        // 正确 这与iostream类中提供的类型转换相同,所以可以这样编写代码: ifstream inputFile("datafile.dat");   if (inputFile) ...                   // 测试inputFile是否已经被                                      // 成功地打开。 象所有的类型转换函数一样,它有一个缺点,在一些情况下虽然大多数程序员希望它调用失败,但是函数还能够成功地被调用(参见条款5)。特别是它允许灵巧指针与完全不同的类型之间进行比较: SmartPtr<Apple> pa; SmartPtr<Orange> po;   ...   if (pa == po) ...                    // 这能够被成功编译! 即使在SmartPtr<Apple> 和 SmartPtr<Orange>之间没有operator= 函数,也能够编译,因为灵巧指针被隐式地转换为void*指针,对于内建指针类型有一个内建的比较函数。这种进行隐式类型转换的行为特性很危险。(再看一下条款5,必须反反复复地阅读,做到耳熟能详。) 在void*类型转换方面,也有一些变通之策。有些设计者采用到const void*的类型转换,还有一些采取转换到bool的方法。这些变通之策都没有消除混合类型比较的问题。 有一种两全之策可以提供合理的测试空值的语法形式,同时把不同类型的灵巧指针之间进行比较的可能性降到最低。这就是在灵巧指针类中重载operator!,当且仅当灵巧指针是一个空指针时,operator!返回true: template<class T> class SmartPtr { public:   ...   bool operator!() const;            // 当且仅当灵巧指针是   ...                                // 空值,返回true。   }; 客户端程序如下所示: SmartPtr<TreeNode> ptn;   ...   if (!ptn) {                          // 正确   ...                                // ptn 是空值 } else {   ...                                // ptn不是空值 } 但是这样就不正确了: if (ptn == 0)......[阅读全文]

posted @ | Feedback (0) |

摘要:     译者注:由于我无法在文档区贴上图片(在论坛询问,结果无人回答),所以只能附上此译文的word文档。下载 这种技术能给我们几乎所有想要的行为特性。假设我们用一个新类CasSingle来扩充MusicProduct类层次,用来表示cassette singles。修改后的类层次看起来象这样: 现在考虑这段代码:template<class T>                    // 同上, 包括作为类型class SmartPtr { ... };              // 转换操作符的成员模板 void displayAndPlay(const SmartPtr<MusicProduct>& pmp,            ......[阅读全文]

posted @ | Feedback (0) |