代码中的MyString
类包含一个指针成员,他指向动态分配的内存。复制这个类的对象时,将复制其指针成员,但不会复制指针指向的缓冲区,其结果会造成两个对象指向同一块内存区,这称为浅复制,会威胁程序的稳定性。
#include<iostream>
using namespace std;
class MyString{
private:
char *Buffer;
public:
MyString(const char *ini){
if(ini!=NULL){
Buffer=new char[strlen(ini)+1];
strcpy(Buffer,ini);
}
else{
Buffer=NULL;
}
}
~MyString(){
cout<<"析构函数调用"<<endl;
if(Buffer!=NULL){
delete[] Buffer;
}
}
int GetLnegth(){
return strlen(Buffer);
}
const char*GetString(){
return Buffer;
}
};
void UseString(MyString s){
cout<<s.GetLnegth()<<endl;
cout<<s.GetString()<<endl;
return;
}
int main(){
MyString s1("this is string one");
UseString(s1);
return 0;
}
函数崩溃出错,与上一篇博文最后一段代码相比,只是将输出代码写进了void UseString(String s)
函数中。运行时,主函数中Mystring
的对象s1
被复制到函数UseString(String s)
的形参中并被使用,而函数void UseString(String s)
被声明为按值传参,因此s1
的指针成员的值被复制到s
中,就是说s1.Buffer
和s.Buffer
指向同一内存单元。
函数void UseString(String s)
返回时,变量s
不再在作用域内,因此被销毁–调用Mystring
的析构函数(代码18-24行);delete[] Bufffer
释放内存。这将导致主函数中的s1.Buffer
指向的内存无效,而等到main()
函数执行完后,s1
不再在作用域内而被销毁时(实际上该内存已被销毁,不能再次被销毁)出错。
一、使用复制构造函数确保深复制
复制构造函数在对象被复制(包括对象按值传递给函数)时被编译器调用。
申明语法如下:
#include<iostream>
using namespace std;
class MyString{
private:
char *Buffer;
public:
MyString(const char *ini){
if(ini!=NULL){
Buffer=new char[strlen(ini)+1];
strcpy(Buffer,ini);
cout<<"Buffer points to :0x"<<hex;
cout<<(unsigned int*)Buffer<<endl;
}
else{
Buffer=NULL;
}
}
MyString(const MyString& CopySource){
cout<<"复制构造函数调用"<<endl;
if(CopySource.Buffer!=NULL){
Buffer=new char[strlen(CopySource.Buffer)+1];
strcpy(Buffer,CopySource.Buffer);
cout<<"Buffer points to :0x"<<hex;
cout<<(unsigned int*)Buffer<<endl;
}
else{
Buffer=NULL;
}
}
~MyString(){
cout<<"析构函数调用"<<endl;
if(Buffer!=NULL){
delete[] Buffer;
}
}
int GetLnegth(){
return strlen(Buffer);
}
const char*GetString(){
return Buffer;
}
};
void UseString(MyString s){
cout<<s.GetLnegth()<<endl;
cout<<s.GetString()<<endl;
return;
}
int main(){
MyString s1("this is string one");
UseString(s1);
return 0;
}
输出:
Buffer points to :0x013D9E20
复制构造函数调用
Buffer points to :0x013D9520
12
this is string one
析构函数调用
析构函数调用
请按任意键继续. . .
两个buffer
的地址分别是0x013D9E20
和0x013D9520
,确认是深复制。
- 类包含原始指针成员时,务必编写复制构造函数和复制赋值运算符;
- 编写复制构造函数时,务必接受源对象的参数申明为const引用;
二、移动构造函数
在需要多次调用复制构造函数来传值时,编译器严格调用复制构造函数反而降低了性能,为了避免这种性能瓶颈,可使用移动构造函数,语法如下:
MyString (MyString && MoveSource){
if(MoveSource.Buffer!=NULL){
BUffer=MoveSource.BUffer;
MoveSource.BUffer=NULL;
}
}
三、几种特殊用途
3.1不允许复制对象的类
通过私有的复制构造函数,确保类对象不能作为参数被复制,只需将函数声明为私有的,无需实现。
class Present{
private:
Present(const Present&);
Present & operator=(const Present&);
}
3.2只能有一个实例的单例类
私有构造函数,私有赋值运算符,静态实例成员
#include<iostream>
# include<string>
using namespace std;
class Present{
private:
Present(){};
Present(const Present&);
const Present&operator=(const Present&);
string name;
public:
static Present&GetInstantance(){
static Present OnlyOne;
return OnlyOne;
}
string getName(){
return name;
}
void SteName(string s){
name=s;
}
};
int main(){
Present & OnlyOne=Present::GetInstantance();
OnlyOne.SteName("baby");
cout<<"the name of the only present is : "<<Present::GetInstantance().getName();
return 0;
}