在写这篇博客之前,笔者已经知道(2)构造函数初始化列表是在(3)构造函数体之前运行的,并且如果有多个成员,则按照成员声明的顺序依次初始化,而和初始化列表里面的先后顺序无关。笔者主要是不明白(1)声明处初始化和另外两处初始化的先后顺序。
一、先写一个小demo验证一下我说的第一句话。
#include<iostream>
#include<memory>
using namespace std;
class Type
{
public:
explicit Type(int data):mData(data)
{
cout<<"Type(int data):"<<this<<" mData="<<mData<<endl;
}
Type(const Type& o)
{
mData = o.mData;
cout<<"Type(const Type& o):"<<this<<" mData="<<mData<<endl;
}
Type& operator=(const Type& o)
{
cout<<"Type& operator=(const Type& o):"<<this<<" mData="<<o.mData<<endl;
if(&o == this)
return *this;
mData = o.mData;
return *this;
}
~Type()
{
cout<<"~Type():"<<this<<" mData="<<mData<<endl;
}
private:
int mData;
};
class Container
{
public:
Container():mType2(new Type(2)),mType3(new Type(3)),mType1(new Type(1))
{
mType0 = make_shared<Type>(0);
mType4 = make_shared<Type>(4);
cout<<"Container()"<<endl;
}
~Container(){cout<<" ~Container()"<<endl;}
private:
shared_ptr<Type> mType0;
shared_ptr<Type> mType1;
shared_ptr<Type> mType2;
shared_ptr<Type> mType3;
shared_ptr<Type> mType4;
};
int main()
{
Container c;
return 0;
}
运行结果是:
Type(int data):0x561c60babe70 mData=1
Type(int data):0x561c60bac2c0 mData=2
Type(int data):0x561c60bac300 mData=3
Type(int data):0x561c60bac350 mData=0
Type(int data):0x561c60bac370 mData=4
Container()
~Container()
~Type():0x561c60bac370 mData=4
~Type():0x561c60bac300 mData=3
~Type():0x561c60bac2c0 mData=2
~Type():0x561c60babe70 mData=1
~Type():0x561c60bac350 mData=0
可以看见,尽管第35行是按照2-3-1的顺序写的,实际上初始化还是按照声明的1-2-3顺序来执行的。还可以看见,初始化列表里面的1-2-3初始化完成后,才会执行构造函数体里面的初始化代码。从打印出来的最后5行还可以看出来,析构的时候是按照声明的逆序来析构那5个智能指针的。
二、再看看 = 在什么情况下是调用拷贝赋值运算符,在什么情况下调用构造函数
#include<iostream>
using namespace std;
class Type
{
public:
explicit Type(int data):mData(data)
{
cout<<"Type(int data):"<<this<<" mData="<<mData<<endl;
}
Type(const Type& o)
{
mData = o.mData;
cout<<"Type(const Type& o):"<<this<<" mData="<<mData<<endl;
}
Type& operator=(const Type& o)
{
cout<<"Type& operator=(const Type& o):"<<this<<" mData="<<o.mData<<endl;
if(&o == this)
return *this;
mData = o.mData;
return *this;
}
~Type()
{
cout<<"~Type():"<<this<<" mData="<<mData<<endl;
}
private:
int mData;
};
int main()
{
Type type1(1);
Type type2 = Type(2);
type1 = Type(3);
return 0;
}
运行结果是:
Type(int data):0x7ffcfe37c96c mData=1
Type(int data):0x7ffcfe37c970 mData=2
Type(int data):0x7ffcfe37c974 mData=3
Type& operator=(const Type& o):0x7ffcfe37c96c mData=3
~Type():0x7ffcfe37c974 mData=3
~Type():0x7ffcfe37c970 mData=2
~Type():0x7ffcfe37c96c mData=3
注意看,输出的第1行、第4行、第7行的内存地址是一样的。从运行结果可以看出来:第34行 Type type2 = Type(2);是调用Type类的构造函数给=号左边的type2初始化,这里并没有生成一个临时对象。第35行type1 = Type(3);则是先在=号右边生成一个临时对象,再把这个临时对象作为参数传进type1调用的Type& operator=(const Type& o),这是一句拷贝赋值的操作。第35行运行结束之后,临时对象析构打印出输出的第5行。
三、看看(1)相对于(2)和(3)的执行顺序
#include<iostream>
#include<memory>
using namespace std;
class Type
{
public:
explicit Type(int data):mData(data)
{
cout<<"Type(int data):"<<this<<" mData="<<mData<<endl;
}
Type(const Type& o)
{
mData = o.mData;
cout<<"Type(const Type& o):"<<this<<" mData="<<mData<<endl;
}
Type& operator=(const Type& o)
{
cout<<"Type& operator=(const Type& o):"<<this<<" mData="<<o.mData<<endl;
if(&o == this)
return *this;
mData = o.mData;
return *this;
}
~Type()
{
cout<<"~Type():"<<this<<" mData="<<mData<<endl;
}
private:
int mData;
};
class Container
{
public:
Container():mType7(new Type(7)),mType4(new Type(4)),mType5(new Type(5)),mType3(new Type(3))
{
mType2= make_shared<Type>(2);
cout<<"Container()"<<endl;
}
~Container(){cout<<" ~Container()"<<endl;}
private:
shared_ptr<Type> mType1 = make_shared<Type>(1);
shared_ptr<Type> mType2;
shared_ptr<Type> mType3;
shared_ptr<Type> mType4;
shared_ptr<Type> mType5;
shared_ptr<Type> mType6= make_shared<Type>(6);
shared_ptr<Type> mType7;
};
int main()
{
Container c;
return 0;
}
运行结果是:
Type(int data):0x562184c94e80 mData=1
Type(int data):0x562184c952a0 mData=3
Type(int data):0x562184c952e0 mData=4
Type(int data):0x562184c95320 mData=5
Type(int data):0x562184c95370 mData=6
Type(int data):0x562184c95380 mData=7
Type(int data):0x562184c953d0 mData=2
Container()
~Container()
~Type():0x562184c95380 mData=7
~Type():0x562184c95370 mData=6
~Type():0x562184c95320 mData=5
~Type():0x562184c952e0 mData=4
~Type():0x562184c952a0 mData=3
~Type():0x562184c953d0 mData=2
~Type():0x562184c94e80 mData=1
从结果可以看出来,构造函数体里面的初始化代码是在最后执行的。(1)[声明处初始化]和(2)[构造函数初始化列表初始化]之间并没有绝对的先后顺序。而是根据成员的声明顺序来执行的。