- 了解C++默默编写并且调用哪些函数
- 一个空类,C++处理过后会默认加上一个复制构造函数,复制赋值操作符和析构函数,但是只有当这些函数被需要被调用的时候才会被C++创建,平时的话一个空类不被使用也就是一个空类了。
- 编译器生成的copy构造函数用法
-
默认的这两个函数仅仅是将对象的成员进行简单赋值。
这就意味着,如果该类的成员是指针类型,那么当该类的两个对象之间发生赋值后,这两个对象中的指针就会指向同一片内存区。这种情况下,内存管理就会复杂起来,后面的文章中会详细讲解如何解决这个问题 - 在类的不同对象之间共享内存区。
有时候,如果你不需要在内存中实现共享,完全可以自行定义这两个函数,为被赋值的对象分配新的内存区。
那么,如果类成员是引用类型呢?如果对象之间成员只是简单的复制,那么岂非出现一个引用型变量引用了新的变量?这是违反 C++ 的”不允许引用型变量改指向不同的对象“这一原则的。
-
- 若不想使用编译器自动生成的函数,就应该明确拒绝
- 比如类自动生成的copy构造函数和赋值操作符函数,我们不想要让类可以直接的调用他们,我们可以将copy构造函数和赋值操作符函数声明为private,这样就不能直接访问并且调用他们了。但是有时候利用friend函数或者是成员函数是可以调用copy函数的,所以我们可以先创建一个基类,这个基类中把copy构造函数和=赋值操作符函数声明为private,然后我们再在自己的类中去继承这个类,这样的话就能够解决这个问题了。这个基类必须是一个空基类。
-
class baseclass{ private: baseclass(){} ~baseclass(){} public: baseclass(const baseclass &); baseclass & operator =(const baseclass &); }; class myclass :private baseclass{ };
- 为多态基类声明virtual析构函数
- 产生问题:当一个基类派生了多个类的时候,而且基类中有一个factory函数返回的是一个基类的指针,指向的是派生类的对象,那么产生的指针肯定哟啊被销毁,所以派生类的那个对象是经由一个基类的指针去消除的,此时如果基类中的析构函数是非虚的话,那么这将会是灾难性的。会发生销毁的时候基类指针销毁了但是派生类的对象没有被销毁,这就可能会造成资源泄漏或者其他的问题。
- 解决方法:给这个基类一个virtual析构函数,一般的话只要class带有virtual函数,那么他就必须要有virtual析构函数
- virtual的实现:拥有虚函数的class通常都会有一个虚指针表,这个表记录了类指针的信息,当对象去调用某一个虚函数时,实际上都是调用的vptr中的vtbl。
- 析构函数的运作方式
- 最深层的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用。
- 如果class的设计目的不是作为base class使用的话,或者不是为了多态,就不该声明virtual析构函数。
- 别让异常逃离析构函数
- 产生问题:当一个类中的析构函数有异常处理机制,当异常产生,调用异常中的函数,如果调用导致了异常,那么该析构函数就会传播异常,让异常离开这个析构函数,抛出异常,析构函数是不允许抛出异常的,因为这会产生很多麻烦。会草率的结束这个程序。
- 解决方案:①通过调用abort()函数去强迫结束程序,不让异常抛出。
Class::~Class(){ try{ db.close(); }catch(...){ std::abort(); } }
②:吞下因为调用而产生的异常
- 或者我么还是可以重新写一个函数去调用析构函数中的方法,再把析构函数中的异常处理机制修改修改。函数抛出异常必须是来自于析构函数之外的函数。
- 析构函数绝对不要吐出任何异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们或结束程序;如果客户需要对某个操作函数运行期间抛出的异常作出反应,那么class'必须新建立一个普通的函数执行该操作
- 绝对不在构造函数和析构函数中调用virtual函数
- 令operator=返回一个reference to *this
-
Class & operator = (cosnt Class & rhs){//返回一个引用reference this.rhs=rhs; //指向当前对象 return *this; //返回左侧的对象 }
-
- 在operator=中处理自我赋值
- 自我赋值这个东西很容易写错。*p=*y,如果p和y同时指向一个东西,那么这个就是自我赋值。
-
widger& widget::operator=(const widget & rhs){//一份不安全的=版本 delete pb; //其中。bitmap *pb; 停止使用当前的bitmap pb=new bitmap(*rhs.pb);//使用rhs的副本pb return *this; //这里可能rhs和this是同一个对象 } //增加认同测试的改正版 widger& widget::operator=(const widget & rhs){//一份不安全的=版本 if (this==&rhs)return *this;//这个版本还是存在异常的问题,如果new bitmap出现异常,指针会指向一块已经被delete的部分, delete pb; pb=new bitmap(*rhs.pb); return *this; } //处理异常的方法,同时也可以解决自我赋值的问题 widger& widget::operator=(const widget & rhs){ bitmap *o=pb;//新建一个指针记住原来的pb指针所指对象 pb=new bitmap(*rhs.pb);//进行赋值操作 delete o;//删除原来的指针所指的内容和指针 return *this; } //最完整的写法 class widget{ void swap(widget &rhs);//交换函数 }; widger& widget::operator=(const widget & rhs){ widget temp(rhs);//赋值构造函数,产生临时的指针变量 swap(temp); return *this; }
- 复制对象的时候不能忘记其每一个成分
- 产生问题:当自己写的copy构造函数和赋值操作符函数不会自动的复制你在后面添加的一些成员变量,不会自动的进行复制。你必须修改你的复制构造函数和构造函数等等,才能将新添加的变量全部复制过去。而且当一个类继承了另一个类,当要全部复制的时候,还必须将父类的一些成员变量也全部复制过去,不能遗漏。
- 复制每一个成分.①复制所有的local成员变量 ②调用所有base class内的适当的copy函数,以防漏掉了基类里面的成员变量
- 注意不能在copy assignment操作符调用coopy构造函数,同时反过来也是不可取的。
- copy函数应该确保复制对象内的素有成员变量以及所有base class成分
不要尝试以某个copyig函数去实现另一个copying函数。应该讲共同机能放进第三个函数中,并由两个copying函数共同调用