构造函数constructor 与析构函数destructor(五)

我们知道当调用默认拷贝构造函数时,一个对象对另一个对象初始化时,这时的赋值时逐成员赋值。这就是浅拷贝,当成员变量有指针时,浅拷贝就会在析构函数那里出现问题。例如下面的例子:

 1 //test.h
 2 #ifndef MYSTRING_H
 3 #define MYSTRING_H
 4 class MyString
 5 {
 6     char* m_str;
 7 public:
 8     MyString(char* str="");
 9     ~MyString();
10     void Display();
11 };
12 #endif //MYSTRING_H
13 
14 
15 //test.cpp
16 #define _CRT_SECURE_NO_WARNINGS
17 #include "MyString.h"
18 #include<iostream>
19 #include<cstring>
20 using std::endl;
21 using std::cout;
22 
23 MyString::MyString(char* str)
24 {
25     m_str = new char[strlen(str) + 1];
26     memset(m_str, 0, strlen(str) + 1);
27     strcpy(m_str,str);
28 }
29 
30 
31 MyString::~MyString()
32 {
33     cout << "destructor" << endl;
34     delete[] m_str;
35     //m_str = 0;
36     
37 }
38 
39 void MyString::Display(){
40     cout << m_str << endl;
41 }
42 
43 //demo.cpp
44 #include"MyString.h"
45 #include<iostream>
46 using std::endl;
47 using std::cout;
48 
49 int main(){
50     MyString a("aaaaaa");
51     MyString b = a;//调用默认拷贝构造函数,此时b对象,和a对象都是指向同一块内存空间,当析构函数调用时,那么同一块内存空间会被释放两次,从而产生运行时错误。
52     a.Display();
53 
54     return 0;
55 }

所以此时默认拷贝构造函数就出现问题,此时应该提供自己的拷贝构造函数,来实施深拷贝。

在类里面添加拷贝构造函数

void MyString::AllocMemAndCpy(char* other){//类里面的工具函数,负责分配内存和拷贝内存中的值
    int len = strlen(other) + 1;
     m_str = new char[len];
    memset(m_str,0,len);
    strcpy(m_str,other);
}

MyString::MyString(const MyString& other){//拷贝构造函数
    AllocMemAndCpy(other.m_str);
}
 1 #include"MyString.h"
 2 #include<iostream>
 3 using std::endl;
 4 using std::cout;
 5 
 6 int main(){
 7     MyString a("aaaaaa");
 8     MyString b = a;
 9     a.Display();
10     b.Display();
11     return 0;
12 }

此时运行结果就是正确的了,不会出现运行时错误了。

注意:当类中的成员变量有指针时,就要小心了。此时可能就需要拷贝构造函数了,还有就是类中有共享内存时,这是要在析构函数中计算还剩下多少对象,只有剩下最后一个对象被销毁时才能delete那块共享内存。

默认的赋值运算符也是浅拷贝,如果不添加自己的等号运算符,这个程序还是会出现运行时错误

 1 #include"MyString.h"
 2 #include<iostream>
 3 using std::endl;
 4 using std::cout;
 5 
 6 int main(){
 7     MyString a("aaaaaa");
 8     MyString b = a;
 9     a.Display();
10     b.Display();
11 
12     MyString c;
13     c.Display();
14     c = a;//程序在此处调用了默认的赋值运算符(=operator),默认的赋值运算符也是浅拷贝
15           //因此这时出现的运行时错误,也是在调用析构函数时,销毁两次同一块内存,为了使剩下成功运行,必须提供自己的等号运算符。
16     
17     return 0;
18 }

上面的代码需要特别注意的是c=a;,这种赋值会造成被删除的两个指针指向同一块内存,在析构函数调用时,同一块内存会被析构函数释放两次。但是如果c=c;,这句代码就不会造成上述的错误,因为自己给自己赋值并没有使两个对象的指针同时指向一个块内存,就自然只会调用一次析构函数,从而释放一次对象。但是为了顾及中情况,应该添加如下的赋值运算符函数。

1 MyString& MyString::operator=(const MyString& other){
2     if (this ==&other)//赋值对象与被赋值对象是同一个对象。即语句a=a;
3         return *this;
4     delete m_str;//因为赋值对象与被赋值对象不是同一个对象,因此 被赋值对象之前指向的内容应该被删除,即a=c;那么a之前指向的内存应该被释放。
5     AllocMemAndCpy(other.m_str);
6     return (*this);
7 }

禁止拷贝:

有时候有些对象时独一无二的,那么是独一无二的对象就禁止拷贝,禁止拷贝的办法就是把operator=() 与拷贝构造函数都声明成私有的,甚至是空函数都可以

 1 #ifndef MYSTRING_H
 2 #define MYSTRING_H
 3 class MyString
 4 {
 5     char* m_str;
 6     void AllocMemAndCpy(char* other);
 7     MyString(const MyString& ms){}
 8     MyString& operator=(const MyString& other){}
 9 
10 public:
11     MyString(char* str="");
12     ~MyString();
13     
14     
15     void Display();
16 };
17 #endif //MYSTRING_H

此时拷贝构造函数和operator=()都是声明在private中,并且都是空函数。此时Test a=c; b=c;这样的语句在编译时就会报错。

我们知道当我们不提供构造函数时,编译器会提供默认构造函数,当我们不提供拷贝构造函数时,编译器会提供默认拷贝构造函数,那么再创建一个空的类时,编译器到底提供什么呢?

1 class Empty {};
2 Empty();             // 默认构造函数
3 Empty( const Empty& );    // 默认拷贝构造函数
4 ~Empty();             // 默认析构函数
5 Empty& operator=( const Empty& );  // 默认赋值运算符
6 Empty* operator&();                       // 取址运算符
7 const Empty* operator&() const;        // 取址运算符 const

上面的就是一个空类,我们在使用时要注意,有时候这些编译器默认提供的不适合,我们要自己写。

原文地址:https://www.cnblogs.com/cplinux/p/5619835.html