复制构造函数和隐式转换 ---临时变量问题

来源:互联网 发布:淘宝裤子追加评价语 编辑:程序博客网 时间:2024/06/05 01:08

什么是复制构造函数和隐式转换(类类型)?

复制构造函数:
c++ primer 第四版上写的是
特殊的构造函数,具有单个形参(常用const修饰),是对该类型的引用.
1.单个形参的构造函数,这是一个类类型:形参是该类的类类型
2.传递的是引用.
如果只是传递该类的类类型,没有加引用,会报错:
例如:
class A
{
A(A a)
{
};
};
error: invalid constructor; you probably meant ‘A (const A&)’

隐式转换(类类型):
可以用单个形参,来调用的构造函数定义了从形参类型到该类类型的一个隐式转换.

在产生临时变量方面,隐式转换和复制构造函数上是一样的.不同的是,隐式转换涉及的是不同类型之间的转换,而复制构造函数是同类类型之间.


什么时候会调用复制构造函数?


1.显示调用

直接调用.A a(100);

2."=" 赋值

在赋值过程中出现的临时变量问题.同时有时可能在赋值的右边又有函数的返回.(前提是没有重载"=")

3.函数形参的隐式转换,形参的复制.

使用形参进行参数传递时的过程.
1.形参为非引用类型的时候,将复制实参的值.
2.当形参或返回值为类类型时,由复制构造函数进行复制.

4.初始化容器元素

复制构造函数可用于初始化顺序容器中的元素. 例如;
vector<string> vect(5);
首先使用默认构造函数初始化一个临时变量,然后用临时变量复制给vect中的每个元素(调用复制构造函数,分配新的空间),最后释放临时变量.
除非想使用容器的默认初始值, 更有效的方法是分配一个空容器,并将已知元素加入容器.

如果是vector的其他定义方式呢?待补充

5.构造函数和和数组元素

如果没有为类类型数组,提供元素初始化式,则将用默认构造函数初始化每个元素.如果使用常规的花括号括住初始化列表来提供显示元素初始化式,则使用复制构造函数初始化每个元素.

6.合成的复制构造函数

1)

如果没有定义复制构造函数,编译器会给我们合成一个,即使有其他的构造函数存在,也会合成复制构造函数.

合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本.

"逐个成员"是指编译器将现有对象的每个非static成员,依次复制到正在创建的对象.

数组成员的复制是个例外.一般不能复制数组,但是如果一个类具有数组成员,则合成复制构造函数将复制数组.

这里有个疑问,如果已经存在合成复制构造函数,且,没有这个构造函数并没有初始化对象中的变量,此时,编译器是否会自动初始化为传入对象值的副本,或者只是初始化为默认值?

初始化为默认值.自己加了点代码测试了下.如果包含类类型,则会调用默认构造函数,如果是内置类型,初始化内置类型:

C++会对他们默认初始化,初始值可能是0(静态对象),可能是随机数(堆,栈对象).

这也同时说明了在构造函数初始化列表中初始化变量是件很重要的事.


2).定义自己的复制构造函数.

有些类必须对对复制对象时发生的事情加以控制.

有指针变量;

有成员在构造函数中分配地址.


禁止复制

为了防止复制,类必须显示声明其复制构造函数为private;

但类的友元和成员仍然可以复制,如果类的友元和成员也禁止复制时,可以声明但不定义复制构造函数.(声明而不定义成员函数是合法的,但使用未定义成员的额任何尝试将导致连接失败).


复制构造函数的最佳应用

大多数类应该定义复制构造函数和默认构造函数.
不定义复制构造函数和默认构造函数会局限类的使用.
不允许复制的类对象只能作为引用传递给函数或从函数中返回,也不能作为容器元素.



下面的代码中包含了同时隐式转换和复制构造函数的调用.

在复制构造函数方面,下面一般情形分为两种方式.

1.

A a=右边;

2.

A a;

a=右边

查看以上两种情况到底怎么具体运行的.



代码和运行结果

#include <iostream>#include <vector>#include <string>using namespace  std;class A{public:A(){cout<<"A with no para constructor:"<<this<<endl;}A(const A &a){cout<<"A with copy constructor:"<<this<<endl;}A(int  i){value=i;cout<<"A with constructr int:"<<this<<endl;}~A(){cout<<"~A destructor:"<<this<<endl;}int value;};class B{public:B(){cout<<"B with no para constructor:"<<this<<endl;}~B(){cout<<"~B destructor:"<<this<<endl;}int i;A a;A arr[2];string *str;};A func(A temp){return temp;}int main(){cout<<"start to test>>"<<endl;A a;A b;b.value=-1;cout<<"condition 1.1>>"<<endl;//direct copya=b;cout<<"a:"<<a.value<<endl;//direct copy not means will use b,it will only call copy initialization    cout<<"condition 1.2>>"<<endl;A e=b;cout<<"condition 2.1>>"<<endl;//initialiation c with int implicit expresstion.//first convert 102 to object A,then point c point 102 object;A c=102;cout<<"condition 2.2>>"<<endl;//first initial A named d,then assign int to d.//like c,then de autoA d;d=110;cout<<"d.value:"<<d.value<<endl;cout<<"start to test func>>"<<endl;cout<<"condition 3.1>>"<<endl;A a1;func(a1);cout<<"condition 3.2>>"<<endl;A b1;b1=func(a1);cout<<"condition 3.3>>"<<endl;A c1= func(a1);cout<<"condition 4>> test container"<<endl;vector <A> vec(5);cout <<"condition 5>> test array"<<endl;A arr[5]={A(),100};cout<<"condition 6>> test auto synthetic copy function"<<endl;B bc1;bc1.i=100;bc1.a=A();bc1.arr[0]=A();cout<<"test class point:"<<bc1.str<<endl;string temp("temp");bc1.str=&temp;B bc2=bc1;B bc3;bc3=bc1;cout<<"bc1:"<<bc1.i<<" "<<&bc1.a<<" "<<&bc1.arr[0]<<" "<<&bc1.arr[1]<<endl;cout<<"bc2:"<<bc2.i<<" "<<&bc2.a<<" "<<&bc2.arr[0]<<" "<<&bc2.arr[1]<<endl;cout<<"bc3:"<<bc3.i<<" "<<&bc3.a<<" "<<&bc3.arr[0]<<" "<<&bc3.arr[1]<<endl;cout<<"Test point:bc1.str:"<<bc1.str<<" bc2.str:"<<bc2.str<<" bc3.str:"<<bc3.str<<endl;cout<<"end to test"<<endl;return 0;}


运行结果:


start to test>>
A with no para constructor:0x7fffa5060b40
A with no para constructor:0x7fffa5060b50
condition 1.1>>

//这里应该是内存的拷贝,直接将内存单元中内容复制到例外内存单元中.

a:-1
condition 1.2>>

////这里调用的A类中的object A复制构造函数
A with copy constructor:0x7fffa5060b60
condition 2.1>>

//调用构造函数,隐式转换
A with  constructr int:0x7fffa5060b70
condition 2.2>>

//这里先创建一个临时变量,然后内存复制,然后销毁.
A with no para constructor:0x7fffa5060b80
A with copy constructr int:0x7fffa5060b90
~A destructor:0x7fffa5060b90
d.value:110
start to test func>>
condition 3.1>>

////函数参数传递,传入一个临时变量,传出,一个临时变量.
A with no para constructor:0x7fffa5060ba0
A with copy constructor:0x7fffa5060bb0
A with copy constructor:0x7fffa5060bc0
~A destructor:0x7fffa5060bc0
~A destructor:0x7fffa5060bb0
condition 3.2>>

//这里可以猜测,函数传出后,直接内存复制给变量.然后函数传出的对象销毁.
A with no para constructor:0x7fffa5060bd0
A with copy constructor:0x7fffa5060be0
A with copy constructor:0x7fffa5060bf0
~A destructor:0x7fffa5060bf0
~A destructor:0x7fffa5060be0
condition 3.3>>

//注意比较这里,这里少一个析构函数.首先,函数参数传入,在函数栈中有个参数,传出的变量不在函数的栈中,而应该在主程序的栈中,上一个因为b1已经有了,分配了空间.所以传出的变为临时变量,传出的值内存复制给b1后,然后释放空间.而这里的c1,没有分配空间,于是将函数传出的值赋值给c1,使c1指向函数返回的内存地址.
A with copy constructor:0x7fffa5060c10
A with copy constructor:0x7fffa5060c00
~A destructor:0x7fffa5060c10
condition 4>> test container

//测试容器,容器会先创建一个临时变量,用临时变量初始化容器中的每个对象,调用复制构造函数,然后销毁临时对象.
A with no para constructor:0x7fffa5060c20
A with copy constructor:0x1810010
A with copy constructor:0x1810014
A with copy constructor:0x1810018
A with copy constructor:0x181001c
A with copy constructor:0x1810020
~A destructor:0x7fffa5060c20
condition 5>> test array

//测试类数组

//第一个对象用A()对象初始化
A with no para constructor:0x7fffa5060b10

//第二个调用int参数构造函数,隐式转换为A类型对象
A with constructr int:0x7fffa5060b14

//剩余是三个调用默认构造函数
A with no para constructor:0x7fffa5060b18
A with no para constructor:0x7fffa5060b1c
A with no para constructor:0x7fffa5060b20

//测试合成复制构造函数
condition 6>> test auto synthetic copy function

//这里说明一个问题:类先初始化成员变量后再调用的构造函数.所以打印输出时的顺序是先打印A,后打印B.
A with no para constructor:0x7fffa5060ab4
A with no para constructor:0x7fffa5060ab8
A with no para constructor:0x7fffa5060abc
B with no para constructor:0x7fffa5060ab0

//下面连续两个构造再析构,是对应代码中的两个赋值语句
A with no para constructor:0x7fffa5060c30
~A destructor:0x7fffa5060c30
A with no para constructor:0x7fffa5060c40
~A destructor:0x7fffa5060c40


test class point:0x2e9d40

//B bc2=bc1

//调用B合成的复制构造函数,初始化,同时,B类中的A类型对象也是调用A的复制构造函数
A with copy constructor:0x7fffa5060ad4
A with copy constructor:0x7fffa5060ad8
A with copy constructor:0x7fffa5060adc

//B bc3; bc3=bc1;

//调用bc3的默认构造函数,然后将内存中的bc1的信息拷贝到bc3中的内存中,所以只有默认构造函数输出.
A with no para constructor:0x7fffa5060af4
A with no para constructor:0x7fffa5060af8
A with no para constructor:0x7fffa5060afc
B with no para constructor:0x7fffa5060af0

//输出三个对象中地址和对应的值.类类型对象,生成的都是新的地址.
bc1:100 0x7fffa5060ab4 0x7fffa5060ab8 0x7fffa5060abc
bc2:100 0x7fffa5060ad4 0x7fffa5060ad8 0x7fffa5060adc
bc3:100 0x7fffa5060af4 0x7fffa5060af8 0x7fffa5060afc

//指针类型复制时,有点类似int等基本类型,直接复制指针单元的内容,三个对象的指针都指向一个地址.

//所以在定义复制构造函数中,有指针对象时,考虑到这个情况,并想想这个结果是不是我们想要的(只是复制指针变量中的地址,指向同一个地址),还是说再创建对象后,再用重新用指针指向.
Test point:bc1.str:0x7fffa5060b30 bc2.str:0x7fffa5060b30 bc3.str:0x7fffa5060b30
end to test
~B destructor:0x7fffa5060af0
~A destructor:0x7fffa5060afc
~A destructor:0x7fffa5060af8
~A destructor:0x7fffa5060af4
~B destructor:0x7fffa5060ad0
~A destructor:0x7fffa5060adc
~A destructor:0x7fffa5060ad8
~A destructor:0x7fffa5060ad4
~B destructor:0x7fffa5060ab0
~A destructor:0x7fffa5060abc
~A destructor:0x7fffa5060ab8
~A destructor:0x7fffa5060ab4
~A destructor:0x7fffa5060b20
~A destructor:0x7fffa5060b1c
~A destructor:0x7fffa5060b18
~A destructor:0x7fffa5060b14
~A destructor:0x7fffa5060b10
~A destructor:0x1810010
~A destructor:0x1810014
~A destructor:0x1810018
~A destructor:0x181001c
~A destructor:0x1810020
~A destructor:0x7fffa5060c00
~A destructor:0x7fffa5060bd0
~A destructor:0x7fffa5060ba0
~A destructor:0x7fffa5060b80
~A destructor:0x7fffa5060b70
~A destructor:0x7fffa5060b60
~A destructor:0x7fffa5060b50
~A destructor:0x7fffa5060b40


以上结果分析可以得出,在=和函数调用的时候会出现调用复制构造函数的问题.其中涉及了临时变量的问题.

看调用方式,如果,当前对象已经分配了地址空间,则调用复制构造函数时,会产生一个临时变量,临时变量由调用的复制构造函数生成.然后将其内存中的信息复制给当前对象.当前对象一直沿用之前的地址空间,而临时变量会调用析构函数释放空间.

如果没有分配空间,如A a=XXX.这时,调用复制构造函数,产生"临时A类型变量"时,a会指向这个产生的"临时变量".

A a;

A b;

a=func(b);

//相当于以下代码,假设func返回临时变量c

A c=func(b);

a=c;//这里是将c内存中的信息复制给a.

c析构;


或者这么说,对于赋值运算符 "=",左边为类类型.

如果在执行"="语句之前,"="两端的对象都有已经分配内存了,

则"="表明,

1)两边为同类型对象,两个内存之间的内容复制;

2)两遍非同类型对象,会调用相应的构造函数(如果存在的话),隐式转换成该类类型对象产生一个临时变量,然后临时变量与"="左边对象进行内存内容复制,然后,再释放临时对象内存.

如果"="语句之前,左端的对象并没有分配内存,如 A a=XXX 形式;

则会调用复制构造函数或者相应的构造函数(隐式转换),产生一个临时变量,然后这个临时变量初始化"="的左边.


对于函数调用返回,其实就是涉及两次复制,传入参数以及返回值.比较简单.


关于隐式转换

使用explicit关键字.具体:http://blog.csdn.net/cdhql/article/details/17447471





0 0
原创粉丝点击