做过C++开发的程序员,很多都觉得不创建拷贝构造函数,似乎也能得到想要的结果。甚至还有人觉得不创建构造函数也可以,当然这是很新很新的新手了。呵呵。
二年前,我也遇到过这个困惑,是不是要显示的创建一个拷贝构造函数。最后还是实践帮我走了迷茫。为了能帮更多的朋友走出困惑,我对自己的理解做了一个总结,虽不够全面,但在某种程度上还是能理解一些问题的。
故事描述:现在流行的数码相机代替了传统的胶片相机。数码相机使用了感光元器件,我可以不再使用传统胶片,依然可以成像。基于不同技术,感光元器件(没有查到感光元器件的正确英文单词,暂时使用Photoreceptor代替)分为两种,一种是CMOS,一种是CCD。
代码如下:
class CMOS
{
public:
CMOS()
{
len = Coefficient * 35.8;
width = Coefficient * 23.9;
printf("%s/n", "Create a CMOS");
}
CMOS(float Coef)
{
Coefficient = Coef;
len = Coefficient * 35.8;
width = Coefficient * 23.9;
m_pCompany = new char[6];
strcpy(m_pCompany, "Canon");
}
~CMOS()
{
delete m_pCompany;
printf("%s/n", "Destroy a CMOS");
}
public:
void Paint()
{
printf("%s", "Create a picture by CMOS.");
}
void PrintSize()
{
printf("%s%f/n%s%f/n", "CMOS Length Size:", len, "CMOS Width Size:", width);
}
void PrintCompany()
{
printf("%s%s/n", "Company:", m_pCompany);
}
public:
float len;
float width;
float Coefficient;
char *m_pCompany;
};
在没有显示定义拷贝构造函数的前提下,测试一下是否可以完成用一个Class Object来初始化另一个Class Object。
void main()
{
CMOS cmos(1.0);
cmos.PrintSize();
cmos.PrintAddress();
cmos.PrintCompany();
CMOS other = coms;
other.PrintSize();
other.PrintAddress();
other.PrintCompany();
}
输出结果:
CMOS Length Size:35.80000
CMOS Width Size:23.900000
Address of len:0x12ff64 Address of width:0X12ff68
Address of m_pCompany:0x430070
Company:Canon
CMOS Length Size:35.80000
CMOS Width Size:23.900000
Address of len:0x12ff50 Address of width:0X12ff54
Address of m_pCompany:0x430070
Company:Canon
通过输出结果可以看出,虽然并没有显示的定义构造函数,但得到了期待的结果。此外,也会发现指针成员并没有被拷贝一份,而是两个类对象同时指向了同一个地址。这会不会因为作用域的问题,而引发程序异常呢?接下来再做一个实验。
添加一个函数。
void Fun(CMOS &pram)
{
CMOS temp(1.5);
printf("%s/n", "----Print a infomation of temp object----");
temp.PrintSize();
temp.PrintAddress();
temp.PrintCompany();
pram = temp;
}
修改main函数
void main()
{
printf("%s/n", "----Print a infomation of frist object----");
CMOS cmos(1.0);
cmos.PrintSize();
cmos.PrintAddress();
cmos.PrintCompany();
printf("%s/n", "----Print a infomation of copy object----");
CMOS other(cmos);
other.PrintSize();
other.PrintAddress();
other.PrintCompany();
Fun(cmos);
printf("%s/n", "----Print a infomation of frist object again----");
cmos.PrintSize();
cmos.PrintAddress();
cmos.PrintCompany();
printf("%s/n", "----Print a infomation of copy object again----");
other.PrintSize();
other.PrintAddress();
other.PrintCompany();
}
输出结果:
----Print a infomation of frist object----
CMOS Length Size:35.80000
CMOS Width Size:23.900000
Address of len:0x12ff64 Address of width:0X12ff68
Address of m_pCompany:0x431d80
Company:Canon
----Print a infomation of copy object----
CMOS Length Size:35.80000
CMOS Width Size:23.900000
Address of len:0x12ff50 Address of width:0X12ff54
Address of m_pCompany:0x431d80
Company:Canon
----Print a infomation of temp object----
CMOS Length Size:53.700001
CMOS Width Size:35.849998
Address of len:0x12fed4 Address of width:0X12fed8
Address of m_pCompany:0x431d40
Company:Canon
Destroy a CMOS
----Print a infomation of frist object again----
CMOS Length Size:53.700001
CMOS Width Size:35.849998
Address of len:0x12ff64 Address of width:0X12ff68
Address of m_pCompany:0x431d40
Company:Canon
----Print a infomation of copy object again----
CMOS Length Size:35.80000
CMOS Width Size:23.900000
Address of len:0x12ff50 Address of width:0X12ff54
Address of m_pCompany:0x431d80
Company:Canon
通过这次输出的结果,可以完全放心了。
接下来,我们再测试一下Class Object中含有member Class Object的情况。修改代码如下:
class Information
{
public:
Information()
{
len = 35.8;
width = 23.9;
m_pCompany = new char[6];
strcpy(m_pCompany, "Canon");
}
~Information()
{
delete m_pCompany;
}
public:
void PrintSize()
{
printf("%s%f/n%s%f/n", "CMOS Length Size:", len, "CMOS Width Size:", width);
}
void PrintAddress()
{
printf("%s%x %s%x/n%s%x/n","Address of len:0x", &len, "Address of width:0X", &width, "Address of m_pCompany:0x", m_pCompany);
}
void PrintCompany()
{
printf("%s%s/n/n", "Company:", m_pCompany);
}
public:
float len;
float width;
char *m_pCompany;
};
class CMOS
{
public:
CMOS()
{
printf("%s/n", "Create a CMOS");
}
~CMOS()
{
printf("%s/n", "Destroy a CMOS");
}
public:
void Paint()
{
printf("%s", "Create a picture by CMOS.");
}
void PrintSize()
{
m_Info.PrintSize();
}
void PrintAddress()
{
m_Info.PrintAddress();
}
void PrintCompany()
{
m_Info.PrintCompany();
}
private:
Information m_Info;
};
void main()
{
CMOS cmos;
cmos.PrintSize();
cmos.PrintAddress();
cmos.PrintCompany();
CMOS other(cmos);
other.PrintSize();
other.PrintAddress();
other.PrintCompany();
}
输出结果:
CMOS Length Size:35.80000
CMOS Width Size:23.900000
Address of len:0x12ff68 Address of width:0x12ff6c
Address of m_pCompany:0x431d80
Company:Canon
CMOS Length Size:35.80000
CMOS Width Size:23.900000
Address of len:0x12ff5c Address of width:0X12ff60
Address of m_pCompany:0x431d80
Company:Canon
这个结果也是另我们满意的,那样是不是说编译器在后台为我们自动生成了一个拷贝构造函数呢?经过参阅资料后得知,其实编译器在上述两种情况并没有合成出来拷贝构造函数,而是采用内存拷贝的方式进行初始化的。
上述包含member Class Object的例子中我们并没有显示的定义拷贝构造函数,接下来我们进行显示的定义然后看看程序会怎么样执行。
Information(const Information &Info)
{
len = Info.len;
width = Info.width;
m_pCompany = new char[6];
strcpy(m_pCompany, Info.m_pCompany);
}
这次查看程序执行堆栈,结果如下:
Information::Information(const Information & {...}) line 104
CMOS::CMOS(const CMOS & {...}) + 41 bytes
main() line 258 + 12 bytes
可以看出,再调用Information类中拷贝构造函数的之前,在CMOS类中合成出了一个拷贝构造函数。
到这里,先作一个小结。
前提条件:
1、 参与的类中不涉及Base Class。
2、 参与的类中不涉及虚函数。
3、 直接参与类中没有显示的拷贝构造函数。
结论:
1、参与的类中不包含member Class Object,边编译器使用内存拷贝的方式完成由一个Class Object初始化另一个Class Object。
2、参与的类中包含member Class Object,但是该member Class Object没有显示的拷贝构造函数。边编译器使用内存拷贝的方式完成由一个Class Object初始化另一个Class Object。
3、参与的类中包含member Class Object,该member Class Object有显示的拷贝构造函数。边编译器会为直接参与类合成出一个拷贝构造函数,完成由一个Class Object初始化另一个Class Object。
下面为CMOS Object创建一个Base Class。
class Photoreceptor
{
public:
Photoreceptor()
{
printf("%s/n", "Create a Photoreceptor");
}
virtual ~Photoreceptor()
{
printf("%s/n", "Destroy a Photoreceptor");
}
public:
virtual void Paint()
{
printf("%s/n", "Create a picture by Photoreceptor.");
}
};
class CMOS : public Photoreceptor
{
public:
CMOS()
{
printf("%s/n", "Create a CMOS");
}
~CMOS()
{
printf("%s/n", "Destroy a CMOS");
}
public:
void Paint()
{
printf("%s/n", "Create a picture by CMOS.");
}
};
void main()
{
CMOS cmos;
Photoreceptor ph = cmos;
cmos.Paint();
ph.Paint();
}
输出结果:
Create a Photoreceptor
Create a CMOS
Create a picture by CMOS.
Create a picture by Photoreceptor.
通过输出结果可以发现,这里并没有把cmos对象的虚指针拷贝给ph对象。这需要引起注意。
那么,我们是不是要为每个类都要创建拷贝构造函数呢?如果类没有Base Class,也没有虚函数,并且只是基础类型的成员变量,这样就不用再构建出来拷贝构造函数了,编译器会出色的完成拷贝任务。