C++对象模型-构造函数语意学

1.默认构造

因为编译器会尽量的为全部的警告和错误作出解释。但也所以致使了部分状况下的过分解析。
书中给的例子是编译器因为过分解析,使用了类型转换运算符的解析代码,致使隐藏了真正的错误。html

cin << intval;
int temp = cin.operator int();
temp << intval;

分析一下:ios

  • 程序员的目的是实现读取输入,可是误将 >> 写成了 <<, 而istream并无重载 << 运算符,编译器一看这条路不通啊,因而只好按照 << 左移位来解析;
  • but,要想实现左移位,又必须将cin转成整型。那么编译器就去找istream有没有类型转换函数好将cin转成整型后再进行移位操做。若是找到了,那么,cin << intval; 正常执行了,并不报错。但不符合程序员的本意。
  • 为了不这种转换发生,istream中使用了operator void*()来替换operator int()。

要注意因为隐式转换可能形成的不良后果。程序员

(1) 隐式类型转换

隐式类型转换虽然会"暗地里"作一些转换操做,但这种机制的好处也是显而易见的。并且C++为了让这个"暗地里"的隐式操做可以被程序员察觉显形,提供了一个修饰符"explicit",类型转换构造函数和类型转换函数声明前加上explicit关键字将阻止编译器隐式类型转换操做。任未尝试隐式转换的操做都会报错。

以下:函数

#include<iostream>
using namespace std;
class A
{
public:  
    //explicit
    A(int a):m_a(a)
    {
        cout << "construct A from int" << endl;
    }
    //explicit
    operator int()
    {
        cout <<"convert A to int " << end; 
        return m_a;
    }
public:    
    int m_a;
};
int main(void)
{
    A a(5);//显式类型转换构造
    A b = 5;//隐式类型转换构造
    int i = a;//隐式类型转换函数
    return 0;
}
  • A a(5);老是正常的。
  • 第一个explicit注释掉,A a(5);运行正常;打开该注释,编译提示:类型转换失败
  • 第二个explicit注释掉,int i = a;运行正常。 打开该注释 编译提示:非法的存储类,即赋值失败。

(2) 默认构造函数

讨论trivial和notirvial其实就是讨论构造函数存在的必要性。构造本质上要为对象的生成作一些辅助操做。
但若是对象生成的需求仅仅是分配空间就够了,那么构造函数其实也没有什么意义。
构造函数确实不是必须的,甚至于某些状况下,编译器连系统默认构造函数都不会提供
好比有一个类就像C中结构体的同样,struct A* pa = new (sizeof(struct A));就足够,即不须要初始化成员变量,也不须要负责成员变量的构造,更不须要初始化虚函数表,或者虚基类表。那么编译器也没有必要提供一个默认构造画蛇添足了。优化

  • 构造时先构造基类,再构造子类的非基类部分,析构时先析构子类的非基类部分,再析构基类。
  • 初始化表用于指导基类子对象或者成员变量如何初始化,也包括类类型的成员变量。
  • 基类子对象按照继承顺序必须在初始化表中指定构造方式,且必须在子类的非基类成员以前被构造出来
  • 对于类类型成员变量,既能够按照初始化表中指示进行构造,也能够以后在函数体中按照声明顺序依次构造对象成员。
  • 子类构造函数执行时会先按照继承顺序插入调用各基类的构造函数的代码。再调用用户定义的子类的构造部分。
  • 无论子类是否默认构造,其基类子对象或者是子类中的类类型成员即便是默认构造,子类至少要提供默认构造来调用基类的构造函数。除非基类连默认构造都不须要。
  • 基类若是是有参构造,则子类必须显式指定构造函数,由于要指示基类如何构造。提供的默认构造没法作到调用基类有参构造。故而须要自定义构造函数来指导基类构造方式。对于类的类类型成员变量也是如此。

综合来看,做者想要说明的是:在基类或者类成员没有自定义构造函数时默认构造函数的做用,在如下几种条件下会体现即:spa

  • 有类类型成员变量须要被构造时,须要调用类类型成员的默认构造函数来调用该成员对象的默认构造。
  • 有基类子对象须要被构造时,须要调用子类的默认构造函数来调用基类的默认构造
  • 带有虚函数的类,须要其默认构造来初始化每一个对象的vptr,注意:虚函数表和函数指针覆盖是在编译期完成的。
  • 虚继承的类,须要其默认构造来初始化每一个对象的虚基类表,注意:虚基类初始化表是在编译期完成的。

看做者总结的四种状况(没有自定义构造函数的状况下):指针

public class B
{
    B(){}
};
class A
{
    class B b;
};
  • 类类型的成员变量须要调用默认构造,即B没有自定义构造时;
public class B
{
    B(){}
};
class A:public class B
{
};
  • 基类须要调用默认构造,即B没有自定义构造时
class A
{
virtual fun(){}
};
  • 存在虚函数时须要经过默认构造函数来,即A::fun须要动态寻址时
class A : virtual public class B
{
};
  • 存在虚基类时须要经过默认构造函数来,即A中的B子对象须要动态寻址时

2.默认拷贝构造

(1) 默认拷贝构造

类须要执行拷贝构造的三种情形code

  • 明确使用 = ;
  • 对象做为实参;
  • 做为返回值,不考虑编译器优化(部分编译器会把临时对象直接做为有名对象返回,从而减小一次拷贝构造)

注意:拷贝构造的本质是仍是构造,本质操做是初始化操做,而非拷贝操做。
拷贝构造一样被分红了trivial和notirvial,是trivial仍是notrivial和默认构造解释差很少。先理解下几个名词。htm

bitwise copy是编译器默认提供的位拷贝,即memcpy系列,以bit为单位。加个semantics(语意)引伸意义后面讲。
memberwise init即基本类型成员的赋值,以成员为单位。对象

bitwise copy semantics:位拷贝语意,即一个类的拷贝构造过程当中的初始化操做应该是固定且连续的memwise init,不能被安插子对象或者类类型的成员变量的拷贝构造(尽管子对象或者类类型的成员变量内部也是递归mmwise init的),另外也不能因为存在虚函数或者虚基类增长拷贝构造操做而在该类的拷贝构造中额外增长虚表指针或者虚基类表指针的重定位操做。

换句话说:bitwise copy semantics上只能有 POD数据类型C++ POD(Plain Old Data)类型
某些状况下对象不能有bitwise copy semantics,不然拷贝构造会出现问题。即上述的几个不能。

如下四种状况不该该表现出bitwise copy semantics,默认构造必须是notrivial的

  • 含有拷贝构造的基类子对象
  • 类类型成员变量有拷贝构造
  • 类声明有虚函数时
  • 类派生自虚基类时

前两种,基类子对象或者类类型成员的拷贝构造必须被当前对象的拷贝构造调用。默认拷贝构造必须是notrivial。
类存在虚函数表vtbl时或者继承自虚基类时,须要重定虚表指针,默认拷贝构造必须是notirvial的。
也就是默认拷贝构造必需要有才行。

最终要回归trivial和notrivial,不要陷在bitwise copy semanstics里。

(2) 程序转化语意学

相关文章
相关标签/搜索