编译器给类自动提供的成员函数2

72 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

回顾

  • 上一节我们有提到,当我们使用一个类对象去初始化一个对象时,编译器会自动创建一个构造函数,这个构造函数被称为复制构造函数,这个复制构造函数只会将对象成员赋值过去,而不会去执行我们制定的构造函数的语句,这样在很多情况下会导致严重的后果。

举例:

如果我们定义一个静态变量用来统计当前程序某个类的对象数量,我们在构造函数里面对静态变量++,在析构函数里面对静态变量--,当我们使用赋值初始化的方法时,复制构造函数就跳过了我们定义的构造函数,没有对静态变量进行递增,而析构函数会在每个对象消亡的时候调用,那么这个静态变量的值就不对了。

// 定义一个stringBad类
public class stringBad() {
	private:
        static int number_String;
        char * str;
        int len;

    public:
        stringBad();
        ~stringBad();
}

// 方法实现
int stringBad::num_String = 0;
stringBad::stringBad() {
    num_String++;
}

stringBad::~stringBad() {
    num_String--;
}

// 这种情况下就会导致num_String的值不正确

另一种可能出现的错误情况是乱码,因为复制构造函数对数据成员的复制是按值复制的,即如果使用的是指针,就会将地址值复制过去造成两个成员指向同一个内容。这样当其中一个对象被析构函数释放之后,另一个对象将找不到指向的值就会乱码错误甚至两次释放内存的错误。

显示复制构造函数解决问题

  • 要解决上面那种因为复制而造成的问题是使用深度复制,也就是说,复制构造函数应该将指针指向的内容同样复制出来并将新地址赋给新指针。这样每个对象都有自己的内容,就不会造成重复释放内存错误。
// 定义一个stringBad类
public class stringBad() {
	private:
        static int number_String;
        char * str;
        int len;

    public:
        stringBad();
        // 显式构造函数
        stringBad(const StringBad& p);
        ~stringBad();
        
}

// 方法实现
int stringBad::num_String = 0;
stringBad::stringBad() {
    num_String++;
}
            

stringBad::stringBad(const StringBad& p){
    num_String++;
    len = p.len;
    str = new char [len+1];
    // 深度拷贝
    std::strcpy(str,st.str)
}

stringBad::~stringBad() {
    num_String--;
}
  • 我们还可以通过在类声明中禁用复制构造函数
stringBad(const StringBad& p) = delete;

总结:

复制构造函数由编译器提供,在以下这几种情况下被调用:

  • 对象作为函数的参数,以值传递的方式传入函数。
  • 对象作为函数的返回值,以值的方式从函数返回。
  • 使用一个对象给另一个对象初始化。

复制构造函数复制值时都是进行值赋值,对于类中有指针和动态分配空间来说,我们需要提供显式的复制构造函数,通常实现的原则是:

  • 对于值类型的成员进行值复制
  • 对于指针和动态分配的空间,在拷贝中应重新分配空间
  • 对于基类,要调用基类合适的拷贝方法,完成基类的拷贝

复制构造函数和赋值运算符的行为比较相似,却产生不同的结果;复制构造函数使用一个已有的对象创建一个新对象。区分它们的方法是有无新对象的产生。