这样作主要是为了防止内存泄漏,见我hexo博客。git
C++的虚析构函数程序员
“自我赋值”发生在对象赋值给本身时:github
class Widget { ... } Widget w; ... w=w;
a[i]=a[j]; //潜在的自我赋值,若是i和j有相同的值
*px=*py; //潜在的自我赋值,若是px和py刚好指向同一个东西
若是遵循条款13和条款14的忠告,你会运用对象来管理资源,并且你能够肯定所谓“资源管理对象”在copy发生时有正确的举措。这种状况下你的赋值操做符或许是“自我赋值安全的”(self-assignment-safe),不须要额外操心。然而若是你尝试自行管理资源(若是你打算写一个用于资源管理的class就得这样作),可能会掉进“在中止使用资源以前意外释放了它”的陷阱。安全
其实从上面例子来看,彷佛没有太大的问题,但假设你简历一个class来保存一个指针指向一块动态分配的位图(bitmap)hexo
class Bitmap { ... } class Widget { ... private: Bitmap* pb; // 指针,指向一个从heap分配而获得的对象 };
对于每次赋值,咱们要考虑到资源管理,便可能会写出以下的代码:函数
Widget& Widget::operator=(const Widget& rhs) //一份不安全的operator=实现版本 { delete pb; pb = new Bitmap(*rhs.pb); return *this; }
这里自我赋值的问题是,operator=函数内的*this(赋值的目的端)和rhs有多是同一个对象。果然如此,delete就不仅是销毁当前对象的bitmap,它也销毁rhs的bitmap。测试
能够经过“证同测试”达到“自我赋值”的检验目的:this
Widget& Widget::operator=(const Widget& rhs) { if(this == &rhs) return *this; delete pb; pb=new Bitmap(*rhs.pb); return *this; }
然而,这个版本不具有“异常安全性”,考虑在“new Bitmap”致使异常,它将获得一个指针指向一块被删除的Bitmap。spa
解决办法:设计
Widget& Widget::operator=(const Widget& rhs) { Bitmap* pOrig=pb; pb=new Bitmap(*rhs.pb); delete pOrig; return *this; }
class Widget { ... void swap(Widget& rhs); ... }; Widget& Widget::operator=(const Widget& rhs) { Widget temp(rhs); swap(temp); return *this; }
Widget& Widget::operator=(Widget rhs) { swap(rhs); return *this; }
swap动做的典型实现:
namespace std { template<typename T> void swap(T& a, T& b) { T temp(a); a=b; b=temp; } }
当std::swap对你的类型效率不高时,提供一个swap成员函数,并肯定这个函数不抛出异常。
若是你提供一个member swap,也该提供一个non-member swap用来调用前者,对于classes(而非templates),也请特化 std::swap。
调用swap时应针对std::swap使用using声明式,而后调用swap而且不带任何“命名空间资格修饰”
为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西
指针与引用看上去彻底不一样(指针用操做符’*’和’->’,引用使用操做符’.’),可是它们彷佛有相同的功能。指针与引用都是让你间接引用其余对象。你如何决定在何时使用指针,在何时使用引用呢?
任何状况下不能用指向空值的引用,而指针没这样的限制。
不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。由于在使用引用以前不须要测试它的合法性。
如下状况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种状况下,你可以设置指针为空),二是你须要可以在不一样的时刻指向不一样的对象(在这种状况下,你能改变指针的指向)。
若是老是指向一个对象而且一旦指向一个对象后就不会改变指向,那么你应该使用引用。当你重载某个操做符时,你也应该使用引用。
直接从代码层面说明区别,定义一个类
class UPInt { // "unlimited precision int" public: UPInt& operator++(); // ++ 前缀 const UPInt operator++(int); // ++ 后缀 UPInt& operator--(); // -- 前缀 const UPInt operator--(int); // -- 后缀 UPInt& operator+=(int); // += 操做符,UPInts // 与ints 相运算 ... };
前缀操做的自增是用相似如下的代码:
// 前缀形式:增长而后取回值 UPInt& UPInt::operator++() { *this += 1; // 增长 return *this; // 取回值 }
然后缀形式,则是如此:
const UPInt UPInt::operator++(int) { UPInt oldValue = *this; // 取回值 ++(*this); // 增长 return oldValue; // 返回被取回的值 }
后缀的会有个临时对象的产生,效率高低比较明了。
通常状况下,
new operator=先operator new + 后 placement new
,前者用于分配存储空间,后者用于调用构造函数初始化所分配的内存。
在C++中真正的临时对象是看不见的,它们不出如今你的源代码中。创建一个没有命名的非堆(non-heap)对象会产生临时对象。这种未命名的对象一般在两种条件下产生:为了使函数成功调用而进行隐式类型转换和函数返回对象时。
临时对象是有开销的,因此你应该尽量地去除它们,然而更重要的是训练本身寻找可能创建临时对象的地方
主要有如下两个地方:
任什么时候候只要见到常量引用(reference-to-const)参数,就存在创建临时对象而绑定在参数上的可能性
任什么时候候只要见到函数返回对象,就会有一个临时对象被创建(之后被释放)
据我所知,C++控制类的一些trick主要包括构造函数设置为private
+设置static函数调用它们
或设置友元函数/类
。
阻止创建某个类的对象,最容易的方法就是把该类的构造函数声明在类的private域
class CantBeInstantiated { private: CantBeInstantiated(); CantBeInstantiated(const CantBeInstantiated&); ... };
class PrintJob; // forward 声明 // 参见Effective C++条款34 class Printer { public: void submitJob(const PrintJob& job); void reset(); void performSelfTest(); ... friend Printer& thePrinter(); private: Printer(); Printer(const Printer& rhs); ... }; Printer& thePrinter() { static Printer p; // 单个打印机对象 return p; }
class Printer { public: static Printer& thePrinter(); ... private: Printer(); Printer(const Printer& rhs); ... }; Printer& Printer::thePrinter() { static Printer p; return p; }
class Printer { public: class TooManyObjects{}; // 当须要的对象过多时 // 就使用这个异常类 Printer(); ~Printer(); ... private: static size_t numObjects; Printer(const Printer& rhs); // 这里只能有一个printer, // 因此不容许拷贝 }; // (参见Effective C++ 条款27)
// Obligatory definition of the class static size_t Printer::numObjects = 0; Printer::Printer() { if (numObjects >= 1) { throw TooManyObjects(); } 继续运行正常的构造函数; ++numObjects; } Printer::~Printer() { 进行正常的析构函数处理; --numObjects; }
系统自动分配的内存是栈内存,是由系统自动分配、释放。程序员经过new或malloc操做开辟的内存,是堆内存,由程序员经过代码进行分配、释放
有以上的条件,咱们知道,禁止在堆中产生对象,即限制new的功能;要求只在堆中产生对象,即限制系统对象的实例化
让咱们先从必须在堆中创建对象开始提及。为了执行这种限制,你必须找到一种方法禁止以调用“new”之外的其它手段创建对象。这很容易作到。非堆对象(non-heap object)在定义它的地方被自动构造,在生存时间结束时自动被释放,因此只要禁止使用隐式的构造函数和析构函数,就能够实现这种限制。
把这些调用变得不合法的一种最直接的方法是把构造函数和析构函数声明为private。这样作反作用太大。没有理由让这两个函数都是private。最好让析构函数成为private,让构造函数成为public。处理过程与条款26类似,你能够引进一个专用的伪析构函数,用来访问真正的析构函数。客户端调用伪析构函数释放他们创建的对象。
class UPNumber { public: UPNumber(); UPNumber(int initValue); UPNumber(double initValue); UPNumber(const UPNumber& rhs); // 伪析构函数 (一个const 成员函数, 由于 // 即便是const对象也能被释放。) void destroy() const { delete this; } ... private: ~UPNumber(); };
class UPNumber { private: static void *operator new(size_t size); static void operator delete(void *ptr); ... };
灵巧指针是一种外观和行为都被设计成与内建指针相相似的对象,不过它能提供更多的功能。它们有许多应用的领域,包括资源管理(参见条款九、十、25和31)和重复代码任务的自动化(参见条款17和29)
当你使用灵巧指针替代C++的内建指针(也就是dumb pointer),你就能控制下面这些方面的指针的行为:
构造和析构。你能够决定创建灵巧指针时应该怎么作。一般赋给灵巧指针缺省值0,避免出现使人头疼的未初始化的指针。当指向某一对象的最后一个灵巧指针被释放时,一些灵巧指针负责删除它们指向的对象。这样作对防止资源泄漏颇有帮助。
拷贝和赋值。你能对拷贝灵巧指针或设计灵巧指针的赋值操做进行控制。对于一些类型的灵巧指针来讲,指望的行为是自动拷贝它们所指向的对象或用对这些对象进行赋值操做,也就是进行deep copy(深层拷贝)。对于其它的一些灵巧指针来讲,仅仅拷贝指针自己或对指针进行赋值操做。还有一部分类型的灵巧指针根本就不容许这些操做。不管你认为应该如何去作,灵巧指针始终受你的控制。
大多数灵巧指针模板看起来都象这样:
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; // 灵巧指针所指的对象 };