《C++新经典对象模型》之第6章 对象构造语义
- 6.1 继承体系下的对象构造
- 6.1.1 对象的构造顺序
- 6.1.2 虚函数
- 6.1.3 构造函数中对虚函数的调用
- 06.01.cpp
- 6.2 对象复制语义学与析构函数语义学
- 6.2.1 对象的默认复制行为
- 6.2.2 拷贝赋值运算符与拷贝构造函数
- 6.2.3 禁止对象的拷贝构造和赋值
- 6.2.4 析构函数语义
- 06.02.cpp
- 6.3 局部对象、全局对象的构造和析构
- 6.3.1 局部对象的构造和析构
- 6.3.2 全局对象的构造和析构
- 06.03.cpp
- 6.4 局部静态对象、对象数组构造析构和内存分配
- 6.4.1 局部静态对象的构造和析构
- 6.4.2 局部静态对象数组的内存分配
- 06.04.cpp
- 6.5 new、delete运算符与内存高级话题
- 06.05.cpp
- 6.6 临时性对象的详细探讨
- 6.6.1 拷贝构造函数相关的临时性对象
- 6.6.2 拷贝赋值运算符相关的临时性对象
- 6.6.3 直接运算产生的临时性对象
- 06.06.cpp
6.1 继承体系下的对象构造
6.1.1 对象的构造顺序
从父类到子类,从根源到末端。
先成员变量(定义顺序,从上到下),后构造函数(先初始化列表,后函数体)。
析构顺序:与构造顺序相反。
从子类到父类,先析构函数,后成员变量(从下到上)。
C::成员变量1构造函数 //7
C::成员变量2构造函数 //8
C::C() //9B::成员变量1构造函数 //4B::成员变量2构造函数 //5B::B() //6A::成员变量1构造函数 //1A::成员变量2构造函数 //2A::A() //3构造函数初始化列表构造函数函数体A::~A() //7A::成员变量2析构函数 //8A::成员变量1析构函数 //9B::~B() //4B::成员变量2析构函数 //5B::成员变量1析构函数 //6
C::~C() //1
C::成员变量2析构函数 //2
C::成员变量1析构函数 //3
6.1.2 虚函数
给虚函数表指针赋值的语句,编译器会插入到构造函数的函数体之前。
C::C()B::B() A::A()vptr = A::vftable; //虚函数表指针赋值A构造函数初始化列表A构造函数函数体vptr = B::vftable;B构造函数初始化列表B构造函数函数体vptr = C::vftable;C构造函数初始化列表C构造函数函数体
6.1.3 构造函数中对虚函数的调用
(1)构造函数中调用虚函数,并不通过虚函数表来调用,而是直接调用(虚函数有真实地址)。
(2)在构造函数中调用的虚函数从所在类往根类回溯,逐次找这个虚函数,找到哪个就直接调用(静态方式)。
(3)构造函数中调用虚函数时,对象未构造完整,不宜采用虚函数表机制调用虚函数。
(4)不要在类的构造函数和析构函数中调用虚函数。
#include <iostream>
using namespace std;namespace _n1
{class TA{public:TA(){cout << "TA::TA(), this = " << this << endl;}~TA(){cout << "TA::~TA(), this = " << this << endl;}};class TB{public:TB(){cout << "TB::TB(), this = " << this << endl;}~TB(){cout << "TB::~TB(), this = " << this << endl;}};class T1{public:T1(){cout << "T1::T1(), this = " << this << endl;}~T1(){cout << "T1::~T1(), this = " << this << endl;}};class T2 : public T1{public:T2(){cout << "T2::T2(), this = " << this << endl;}~T2(){cout << "T2::~T2(), this = " << this << endl;}};class T3 : public T2{public:T3(){cout << "T3::T3(), this = " << this << endl;}~T3(){cout << "T3::~T3(), this = " << this << endl;}private:TA ta;TB tb;};
}namespace _n2
{class A{public:A(){myvirfunc();cout << "A::A(), this = " << this << endl;}virtual ~A(){myvirfunc();cout << "A::~A(), this = " << this << endl;}public:virtual void myvirfunc(){myvirfunc2();cout << "A::myvirfunc(), this = " << this << endl;}virtual void myvirfunc2(){cout << "A::myvirfunc2(), this = " << this << endl;}};class B : public A{public:B(){myvirfunc();cout << "B::B(), this = " << this << endl;}virtual ~B(){myvirfunc();cout << "B::~B(), this = " << this << endl;}public:virtual void myvirfunc(){myvirfunc2();cout << "B::myvirfunc(), this = " << this << endl;}virtual void myvirfunc2(){cout << "B::myvirfunc2(), this = " << this << endl;}};class C : public B{public:C() : m_c(11){myvirfunc(); // 调用一个虚函数cout << "C::C(), this = " << this << endl;}virtual ~C(){myvirfunc();cout << "C::~C(), this = " << this << endl;}public:int m_c;public:virtual void myvirfunc(){myvirfunc2();cout << "C::myvirfunc(), this = " << this << endl;}virtual void myvirfunc2(){cout << "C::myvirfunc2(), this = " << this << endl;}};
}int main()
{if (0){_n1::T3 t3;}if (0){_n2::C c;}if (1){_n2::A *mycobj = new _n2::C();mycobj->myvirfunc();delete mycobj;}return 0;
}
06.01.cpp
#include <cstdio>
#include <iostream>
using namespace std;struct A
{A() { printf("A::A(), this = %p\n", this); }virtual ~A() {}virtual void myvirfunc2(){printf("A::myvirfunc2()\n");}virtual void myvirfunc(){myvirfunc2();printf("A::myvirfunc()\n");}
};
struct B : A
{B(){myvirfunc();printf("B::B(), this = %p\n", this);}virtual ~B() {}virtual void myvirfunc2(){printf("B::myvirfunc2()\n");}virtual void myvirfunc(){myvirfunc2();printf("B::myvirfunc()\n");}
};
struct C : B
{int m_c;C(): m_c(11){myvirfunc(); // 调用一个虚函数printf("C::C(), this = %p\n", this);}virtual ~C(){myvirfunc();}virtual void myvirfunc2(){printf("C::myvirfunc2()\n");}virtual void myvirfunc(){myvirfunc2();printf("C::myvirfunc()\n");}
};int main()
{// C c;C *mycobj = new C();// mycobj->myvirfunc();delete mycobj;cout << "Over!\n";return 0;
}
6.2 对象复制语义学与析构函数语义学
6.2.1 对象的默认复制行为
无拷贝构造函数和拷贝赋值运算符时,默认的对象复制行为会发挥作用。
6.2.2 拷贝赋值运算符与拷贝构造函数
提供拷贝赋值运算符或拷贝构造函数时,需提供默认构造函数(此时编译器不会生成)。
struct JI
{JI() { cout << "JI::JI()" << endl; }virtual ~JI() { cout << "JI::~JI()" << endl; }
};struct A : JI
{int m_i, m_j;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }A &operator=(const A &tmp){if (&tmp == this)return *this;// static_cast<JI&>(*this) = tmp; // 调用父类的拷贝赋值运算符// JI::operator=(tmp);// 调用父类的拷贝赋值运算符m_i = tmp.m_i;m_j = tmp.m_j;cout << "A::operator=(const A&)" << endl;return *this;}A(const A &tmp)//: JI(tmp) // 显式调用父类拷贝构造函数{// 编译器会插入代码,父类按位复制,调用父类的构造函数或者拷贝构造函数?m_i = tmp.m_i;m_j = tmp.m_j;cout << "A::A(const A&)" << endl;}
};
6.2.3 禁止对象的拷贝构造和赋值
(1)类中声明私有的拷贝构造函数和拷贝赋值运算符,无函数体。
class A {private:A& operator=(const A&);A(const A&);
};
(2)c++11,=delete,将拷贝构造函数和拷贝赋值运算符标记为禁用。
class A {public:A& operator=(const A&) =delete;A(const A&) =delete;
};
6.2.4 析构函数语义
-
析构函数被合成
编译器合成析构函数的情况:
(1)类继承的父类具有析构函数,会合成析构函数并调用父类的析构函数。
(2)类中类类型成员变量具有析构函数,会合成析构函数并调用该成员变量所属类的析构函数。 -
析构函数被扩展
已写析构函数时,向其中增加代码的情况:
(1)类继承的父类具有析构函数,会在析构函数后面插入代码,调用父类的析构函数。
(2)类中类类型成员变量具有析构函数,会在析构函数后面插入代码,调用该成员变量所属类的析构函数。
06.02.cpp
#include <iostream>
using namespace std;struct JI
{JI() { cout << "JI::JI()" << endl; }virtual ~JI() { cout << "JI::~JI()" << endl; }
};struct A : JI
{int m_i, m_j;// private:// A& operator=(const A& tmp);// A(const A& tmptime);// public:// A& operator=(const A& tmp) = delete;// A(const A& tmptime) = delete;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }A &operator=(const A &tmp){if (&tmp == this)return *this;// static_cast<JI&>(*this) = tmp; // 调用父类的拷贝赋值运算符// JI::operator=(tmp);// 调用父类的拷贝赋值运算符m_i = tmp.m_i;m_j = tmp.m_j;cout << "A::operator=(const A&)" << endl;return *this;}A(const A &tmp)//: JI(tmp) // 显式调用父类拷贝构造函数{// 编译器会插入代码,调用父类的构造函数或者拷贝构造函数?m_i = tmp.m_i;m_j = tmp.m_j;cout << "A::A(const A&)" << endl;}
};struct ParC
{virtual ~ParC() { cout << "ParC::~ParC()" << endl; }
};
struct MemC
{ParC m_j;~MemC() { cout << "MemC::~MemC()" << endl; }
};int main()
{{A aobj;aobj.m_i = 15;aobj.m_j = 20;A aobj2 = aobj; // 执行拷贝构造A aobj3;aobj3.m_i = 13;aobj3.m_j = 16;aobj2 = aobj3; // 执行拷贝复制运算符}{MemC mobj;}cout << "Over!\n";return 0;
}
6.3 局部对象、全局对象的构造和析构
6.3.1 局部对象的构造和析构
只要超出了对象的作用域,编译器总会在适当的地方插入调用对象析构函数的代码。
尽量把对象定义在需要立即用到它的代码段的附件。
6.3.2 全局对象的构造和析构
全局对象的初始化和释放过程:
(1)全局对象获得地址(可执行文件中确定的,和堆、栈中分配内存不一样,程序运行期间一直存在)。
(2)全局对象内存清零(静态初始化)。
(3)调用全局对象对应类的构造函数。
(4)执行main函数。
(5)main函数执行完毕后,调用全局对象对应类的析构造函数。
(6)整个可执行程序执行完毕。
06.03.cpp
#include <iostream>
using namespace std;struct A
{int m_i;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }
};void myfunc()
{// A obja; // 这里定义不合适if (1 == 1){// 这里会被编译器插入调用obja对象析构函数的代码,影响执行效率完全没必要return;}A obja; // 这里定义合适obja.m_i = 10;cout << "obja.m_i = " << obja.m_i << endl;return;
}A g_aobj;int main()
{{A obja;int mytest = 1;if (mytest == 0)return 0;myfunc();}g_aobj.m_i = 6; //cout << "Over!\n";return 0;
}
6.4 局部静态对象、对象数组构造析构和内存分配
6.4.1 局部静态对象的构造和析构
只有当函数调用时,局部静态对象的构造函数才会执行。
多次调用,只有第一次才构造。
编译时内存开始地址和大小已确定(BSS段),运行到对应代码时,才从事先约定好的地址分配出来。
构造实现方式:局部静态对象地址旁增加字节做标记(是否已构造),第一次构造,第二次标记存在,不构造,跳过static A s_aobj;代码行。
析构实现方式:第一次构造时,增加代码_atexit登记信息,main函数执行完后执行析构。
6.4.2 局部静态对象数组的内存分配
同局部静态对象类似。
但编译器优化,大数组不分配实际的物理地址,对象数组做有用事情时,才实际分配。
06.04.cpp
#include <cstdio>
#include <iostream>
using namespace std;struct A
{int m_i;// A() { cout << "A::A()" << endl; }//~A() { cout << "A::~A()" << endl; }
};void myfunc1()
{static A s_aobj1;static A s_aobj2;printf("&s_aobj1=%p\n", &s_aobj1);printf("&s_aobj2=%p\n", &s_aobj2);
}
const A &myfunc2()
{static A s_aobj1;printf("&s_aobj1=%p\n", &s_aobj1);return s_aobj1;
}void myfunc()
{static A s_aobj[1000'0000]; // '是数字分隔符, C++14for (int i = 0; i < 1000'0000; i++)s_aobj[i].m_i = i;//大数组做有用事情时,才实际分配内存printf("s_aobj = %p\n", s_aobj);
}int main()
{myfunc1();myfunc1();printf("&A=%p\n", &myfunc2());printf("&A=%p\n", &myfunc2());myfunc();myfunc();cout << "Over!\n";return 0;
}
6.5 new、delete运算符与内存高级话题
void *p = malloc(0);
char *q = new char[0];
new内部调用malloc,两行代码几乎等价。
代码诡异,可能报错,别使用。
06.05.cpp
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;int main()
{void *p = malloc(0);char *q = new char[0];strcpy_s((char *)p, 100, "This is a test1!");strcpy_s(q, 100, "This is a test2!");printf("p=%s\n", p);printf("q=%s\n", q);delete[] q;free(p);cout << "Over!\n";return 0;
}
6.6 临时性对象的详细探讨
6.6.1 拷贝构造函数相关的临时性对象
struct A
{int m_i;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }A(const A &tmp){m_i = tmp.m_i;cout << "A::A(const A&)" << endl;}A &operator=(const A &tmp){m_i = tmp.m_i;cout << "A::operaotor=(const A&)" << endl;//printf("&tmp=%p\n", &tmp);return *this;}
};
A operator+(const A &obj1, const A &obj2)
{// cout << "A operaotor+(const A&, const A &)" << endl;A tmp; // 一次构造函数tmp.m_i = obj1.m_i + obj2.m_i;printf("&tmp=%p\n", &tmp);return tmp; // 一次拷贝构造函数+一次析构函数(编译器优化后可能无临时对象)
}if (1){A myobj1;printf("&myobj1=%p\n", &myobj1);A myobj2;printf("&myobj2=%p\n", &myobj2);A resultobj1 = myobj1 + myobj2;//编译器优化后直接将tmp的地址用作resultobj1的地址,节省一次复制构造+一次析构cout << "------------------------\n";printf("&resultobj1=%p\n", &resultobj1);}
6.6.2 拷贝赋值运算符相关的临时性对象
struct A
{int m_i;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }A(const A &tmp){m_i = tmp.m_i;cout << "A::A(const A&)" << endl;}A &operator=(const A &tmp){m_i = tmp.m_i;cout << "A::operaotor=(const A&)" << endl;//printf("&tmp=%p\n", &tmp);return *this;}
};
A operator+(const A &obj1, const A &obj2)
{// cout << "A operaotor+(const A&, const A &)" << endl;A tmp; // 一次构造函数tmp.m_i = obj1.m_i + obj2.m_i;printf("&tmp=%p\n", &tmp);return tmp; // 一次拷贝构造函数+一次析构函数(编译器优化后可能无临时对象)
}if (1){A myobj1;printf("&myobj1=%p\n", &myobj1);A myobj2;printf("&myobj2=%p\n", &myobj2);A resultobj2;resultobj2 = myobj1 + myobj2;//一次构造函数+一次拷贝赋值运算符+一次析构cout << "------------------------\n";printf("&resultobj2=%p\n", &resultobj2);}
6.6.3 直接运算产生的临时性对象
if (1){A myobj1;A myobj2;myobj1 + myobj2;//一次构造+一次析构}if (1){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;printf("(myobj1 + myobj2).m_i = %d\n", (myobj1 + myobj2).m_i); // 临时对象(operator+,一次构造+一次析构,且析构在printf后执行)}if (1){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;if ((myobj1 + myobj1).m_i > 3 || (myobj1 + myobj2).m_i > 5) // 两个临时对象cout << "Condition established" << endl;elsecout << "Condition not established" << endl;}if (1){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;if ((myobj1 + myobj1).m_i > 1 || (myobj1 + myobj2).m_i > 5) // 一个临时对象cout << "Condition established" << endl;}
编译器会在必要的地方插入代码产生临时对象,完成要实现的意图。
- 临时对象被摧毁
if (0){const char *p = (string("123") + string("456")).c_str(); // error,临时对象被摧毁了printf("p = %s\n", p);string aaa = string("123") + string("456");const char *q = aaa.c_str(); // okprintf("q = %s\n", q);}
- 临时对象因绑定到引用而被保留
if (0){const string &aaa2 = string("123") + string("456");printf("aaa2 = %s\n", aaa2.c_str());}
06.06.cpp
#include <cstdio>
#include <iostream>
using namespace std;struct A
{int m_i;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }A(const A &tmp){m_i = tmp.m_i;cout << "A::A(const A&)" << endl;}A &operator=(const A &tmp){m_i = tmp.m_i;cout << "A::operaotor=(const A&)" << endl;// printf("&tmp=%p\n", &tmp);return *this;}
};
A operator+(const A &obj1, const A &obj2)
{// cout << "A operaotor+(const A&, const A &)" << endl;A tmp; // 一次构造函数tmp.m_i = obj1.m_i + obj2.m_i;printf("&tmp=%p\n", &tmp);return tmp; // 一次拷贝构造函数+一次析构函数(编译器优化后可能无临时对象)
}int main()
{if (0){A myobj1;printf("&myobj1=%p\n", &myobj1);A myobj2;printf("&myobj2=%p\n", &myobj2);A resultobj1 = myobj1 + myobj2; // 编译器优化后直接将tmp的地址用作resultobj1的地址,节省一次复制构造+一次析构cout << "------------------------\n";printf("&resultobj1=%p\n", &resultobj1);}if (0){A myobj1;printf("&myobj1=%p\n", &myobj1);A myobj2;printf("&myobj2=%p\n", &myobj2);A resultobj2;resultobj2 = myobj1 + myobj2; // 一次构造函数+一次拷贝赋值运算符+一次析构cout << "------------------------\n";printf("&resultobj2=%p\n", &resultobj2);}if (0){A myobj1;A myobj2;myobj1 + myobj2;}if (0){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;printf("(myobj1 + myobj2).m_i = %d\n", (myobj1 + myobj2).m_i); // 临时对象(operator+,一次构造+一次析构,且析构在printf后执行)}if (0){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;if ((myobj1 + myobj1).m_i > 3 || (myobj1 + myobj2).m_i > 5) // 两个临时对象cout << "Condition established" << endl;elsecout << "Condition not established" << endl;}if (1){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;if ((myobj1 + myobj1).m_i > 1 || (myobj1 + myobj2).m_i > 5) // 一个临时对象cout << "Condition established" << endl;}if (0){const char *p = (string("123") + string("456")).c_str(); // error,临时对象被摧毁了printf("p = %s\n", p);string aaa = string("123") + string("456");const char *q = aaa.c_str(); // okprintf("q = %s\n", q);}if (0){const string &aaa2 = string("123") + string("456");printf("aaa2 = %s\n", aaa2.c_str());}cout << "Over!\n";return 0;
}