重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
//B内含A
class A {
public:
A();
A(int);
...
}
class B{
public:
B a;
char* str;
}
//由于A有default constructor,而B内含A且B没有任何constructor,因此编译器需要为B合成一个default constructor,让其来调用A的default constructor处理B::a
//此处B中被合成的default constructor样子
inline
B::B()
{
a.A::A();
}
若class B中内含有一个及以上的member class objects,那么class B中的每一个constructor必须调用每一个member class的default constructor;如果是多个,编译器安插代码,会按照member objects在class中声明顺序来调用对应的constructor。因此,编译器会扩张已存在的constructor,在其中安插必要代码,使其先调用member class的default constructor
创新互联建站-专业网站定制、快速模板网站建设、高性价比巴南网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式巴南网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖巴南地区。费用合理售后完善,10年实体公司更值得信赖。
//若我们为B写一个default constructor让其初始化str
B::B() { str = 0; };
//此时会出现一个问题,那就是B中已经存在一个我们写的default constructor,编译器不能为其再合成一个,那么我们便没法调用a的constructor。
//此时,编译器会扩张已存在的constructor,在其中安插必要代码,使其先调用member class的default constructor
//扩张后的B的default constructor内部结构
B::B()
{
a.A::A();
str = 0;
}
class A
{
public:
virtual void do() = 0;
}
void do( const A& a ) { a.do(); };
void act()
{
//B和C派生自A
B b;
C c;
do( b );
do( c );
}
此时编译器会产生两个扩张行为:
并且,a.do()的virtual invocation会被改写
//原本a.do()
( *a.vptr[1] )( &a )
为了让以上机制发挥效果,编译器必须为base和其derived class object的vpty设定初值,指向相关的virtual table地址。这样的任务会交给constructor做扩张
以下三种情况会以一个object的内容作为另一个class object的初值:
显示地以一个object内容作为另一个class object的初值
object被当做参数传给某函数
函数传回class object
//第一种
class A {...};
A a;
A aa = a;
//第二种
void do1( A a );
void do2()
{
A aa;
foo(aa);
}
//第三种
A do3()
{
A a;
return a;
}
若一个class object没有explict copy constructor,而当class object以相同的class的另一个object作为初值时,此class object内部会把每一个内部或derived的data member的值,从另一个object拷贝一份到自己这,再以递归的方式进行memberwise initialization,但它并不拷贝member class object
memberwise initialization这一机制由bitwise copy semantics和default copy constructor实现,当class不展现bitwise copy semantics,编译器才会合成default copy constructor
位拷贝(浅拷贝/bitwise copy semantics):编译器只是直接将data member的值或指针的值拷贝过来,并不拷贝member class object;也就是说这会导致多个指针指向同一对象
若class展现了bitwise copy semantics且该class并没有explict copy constructor,此时编译器不会合成copy constructor
//以下这种情况,因为展现出了bitwise copy semantics,因此编译器并不会合成copy constructor
class A
{
public:
...//不包含explict copy constructor
A(const char*);
~A();
private:
int val;
char* str;
}
A a("example")
void do()
{
A aa = a;
}
//以下这种情况,因为biewise copy semantics无法调用内部class object的copy constructor,因此编译器需要合成一个copy constructor来调用
class A
{
public:
A(const String&);
~A();
private:
int val;
String str;
}
class String
{
public:
String(const char*);
String(const String&);
~String();
}
对于声明了virtual functions的class,编译器都会在编译器为其进行扩张,分别生成一个vptr和一个vtbl。此时用一个新的class类型object赋值给前一个class,bitwise copy semantics不在起作用,不然编译器设定的vptr并不是正确的,此时编译器需要合成default copy constructor。然而,对于一样的class object,bitwise copy semantics依然生效(除开pointer member)
//以下这种情况bitwise copy semantics依然生效,两个class AA实例的vptr将指向同一vtbl
class A
{
public:
A();
~A();
virtual void do1();
private:
//需要的变量
}
class AA : public A
{
public:
AA();
void do1() override;
virtual do2();
private:
//需要的变量
}
void test()
{
AA aa;
AA aa1 = aa;
}
//以下这种情况,两个类型的class object的vptr将指向各自相关的vtbl
A a = aa;
一个class object如果以另一个含有virtual base class subobject作为初值,bitwise copy semantics会失效,因为virtual base class subobject的位置不确定,bitwise copy semantics会破坏这个位置,维护这个位置由编译器完成,因此需要由编译器合成default copyconstructor做出应对行为
有这样一段代码:
class X{...}; //定义了copy constructor
X x;
void do()
{
X x1(x);
X x2 = x;
X x3 = X(x);
}
上面的初始化操作将进行两个必要的可能的程序转化:
//转化后
//可能的转换,不同编译器进行的事儿不同
void do()
{
X x1;
X x2;
X x3;
x1.X::X(x);
x2.X::X(x);
x3.X::X(x);
}
void do( X x );
X x1;
do(x1);
以上代码进行argument initialization,函数会让local instance x 用memberwise将x1当作初值
而对于不同的编译器,这一过程有不同的实现方法:
导入临时object,调用copy constructor:
//需要更改argument,不然需要多进行一次bitwise
void do( X& x );
//编译器产生的临时object
X _tempx;
_tempx.X::X(x1);
do( _tempx );
X do()
{
X xx;
...
return xx;
}
对于以上函数的返回值object,采用两步将局部对象xx拷贝而来:
为函数加一个一个class object 的reference的额外参数,用来放置copy construct的返回值
在return前安插copy constructor调用操作,将传回的object的内容作新增参数的初值
这种方法又被称为named return value(NRV)优化
void do( X& _result )
{
X xx;
xx.X::X();
_result.X::X( xx );
return;
}
//do()的操作调用也随之改变
X x1 = do();
//转为
X x1;
do(x1);
NRV优化的缺点:
是否需要copy constructor?
对于没有member或base class objects带有copy constructor,以及没有virtual base class 或 virtual function的class,并不需要copy constructor,此时memberwise即可满足要求,效率即可也安全,并不会导致memory leak,也没有address aliasing。再者,对于这类class,使用memcpy()会更有效率
class point3d
{
public:
point3d( float x, float y, float z );
private:
float _x, _y, _z;
}
//此时class的copy constructor这样执行更有效率
point3d::point3d( const point3d& rhs )
{
memcpy( this, &rhs, sizeof(point3d) );
};
member initialization list不是一组函数调用,编译器安照member在class中的声明顺序一一操作member initialization list,且在任何user code前,以适当顺序在constructor中安插初始化操作
对于以下四种情况,为保证程序顺利编译,必须使用member initialization list:
以下情况编译可以通过,但效率并不高
class A()
{
String _name;
int _value;
public:
A()
{
_name = 0;
_value = 0;
}
}
//此时A constructor会产生一个临时object,再将其初始化,随后以assignment运算符将临时Object传递给_name,最后摧毁临时object
//以下是扩张
A::A()
{
_name.String::String();
String temp = String(0);
//memberwise拷贝
_name.String::operator=(temp);
temp.String::~String();
_value = 0;
}
对此进行修改
//运用member initialization list
A::A : _name(0)
{
_cnt = 0;
}
//如下扩张
A::A()
{
_name.String::String(0);
_value = 0;
}
若不注意初始化顺序和member initialization list的排列顺序,可能会出现以下错误:
class B
{
int i;
int j;
public:
B(int val) : j(val), i(j)
{
}
};
//执行顺序 i = j; j = val;
以上程序改善:将一个member的初始化操作放在constructor
B::B(int val) : j(val)
{
i = j;
}
对于member function,请不要使用member initialization list将member function作为另一个对象的初值,因为并不知道member function对class object依赖性,因此需要将其放于constructor