《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() //9
B::成员变量1构造函数 //4
B::成员变量2构造函数 //5
B::B() //6
A::成员变量1构造函数 //1
A::成员变量2构造函数 //2
A::A() //3
构造函数初始化列表
构造函数函数体
A::~A() //7
A::成员变量2析构函数 //8
A::成员变量1析构函数 //9
B::~B() //4
B::成员变量2析构函数 //5
B::成员变量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++14
for (int i = 0; i < 1000'0000;