本文所谈的对象是带有指针的类(Class with pointer);这时不能使用编译器自动合成的三大函数(Big three):拷贝构造、拷贝赋值和析构。需要自己去显式的定义着三大函数;
primer中给出的建议是:如果一个类需要需要自定义析构函数,几乎可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数。
所以一般情况下,这三个函数总是一起自定义,或者一起不定义。
在实际中,如果定义了其他构造函数,那么最好也提供一个默认构造函数。
1、拷贝构造函数
定义:如果一个构造函数的第一个参数是自身类类型的引用(必须是引用),且任何额外参数都有默认值,则此构造函数是拷贝构造函数。
并且参数几乎总是const的引用。
对于不含指针的类,合成拷贝构造函数和自定义的拷贝构造函数等价;
拷贝初始化不仅发生在用=定义变量时,下列情况也会发生:
(1)将一个对象作为实参传递给一个非引用类型的形参。
(2)从一个返回类型为非引用类型的函数返回一个对象。
(3)用花括号列表初始化一个数组中的元素或聚合类中的成员。
对于编译器自动生成的合成拷贝构造函数,依次拷贝每个成员变量--copies each member varible(nembers,objects,arrays)。
一旦成员里面有指针,也会拷贝指针,造成的结果就是两个指针指向同一片内存。最终导致类的析构失败。
2、拷贝赋值运算符
拷贝赋值其实就是对赋值运算符重载。
对于赋值运算符,必须定义为成员函数。
赋值运算符通常返回一个指向类对象的引用。
3、析构函数
析构函数释放对象使用的资源,并销毁对象的非static数据成员。
隐式销毁一个内置指针类型的成员不会delete它所指向的对象。
下面的程序来自于--侯捷老师的网络课程《C++面向对象高级开发》
通过程序详细的介绍Class with pointer的设计。
string.h
#ifndef __MYSTRING__
#define __MYSTRING__
#define _CRT_SECURE_NO_WARNINGS
class String
{
public:
//默认构造函数,并设置默认值
String(const char* cstr = 0);
//Big Three三大函数:拷贝构造,拷贝赋值,析构。编译器会自动给出三大函数。
//对于class with pointer,三大函数需要自己写,不能使用编译器给的默认版本。
//不然会出现内存泄漏(memory leak)和别名(alias):多个指针指向同一块内存。
//1. 拷贝构造
String(const String& str);
//2. 拷贝赋值,返回值为String&,可以满足连串赋值的操作
String& operator=(const String& str);
//3. 析构
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
//对传入的指针进行判断
if (cstr) {
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);
}
//若未指定初值
else {
m_data = new char[1]; //为了搭配上面的写法
*m_data = '\0';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
//检查自我赋值(self assignment)
//1. 提高效率
//2. 正确性,如果自我赋值会出错
if (this == &str)
return *this;
//经典写法1-2-3
//1. 先清空自己指向的内存
delete[] m_data;
//2. 分配需要的内存
m_data = new char[strlen(str.m_data) + 1];
//3. 最后拷贝数据
strcpy(m_data, str.m_data);
return *this;
}
//深拷贝,编译器自动给出的为浅拷贝
inline
String::String(const String& str)
{
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
// <<的重载必须写为非成员函数
// 如果写成成员函数,使用者则需要这样写:string << cout; 这样不符合常用习惯。
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
string-test.cpp
#include "string.h"
int main()
{
String str1;
String str2("hello str2");//默认构造
String str3 = "hello str3";//默认构造
String str4(str2);//拷贝构造
String str5 = str4;//拷贝构造,同上
str1 = str2;//赋值构造
//重载<<
cout << "str1:" << str1 << endl;
cout << "str2:" << str2 << endl;
cout << "str3:" << str3 << endl;
cout << "str4:" << str4 << endl;
cout << "str5:" << str5 << endl;
//new:先分配memory,再调用ctor。
String* pstr = new String[3];
//编译器将上面语句翻译为下面三个动作
//1. void* mem = operator new(sizeof(String)*3); 其内部调用malloc(n);
//2. pc = static_cast<String*>(mem);
//3. pc->String::String();调用三次构造
pstr[0] = str1;
pstr[1] = str2;
pstr[2] = str3;
cout << "pstr[1]:" << pstr[0] << endl;
cout << "pstr[2]:" << pstr[1] << endl;
cout << "pstr[3]:" << pstr[2] << endl;
//delete:先调用dtor,再释放memory;
delete[] pstr;
//编译器将上面语句翻译为下面两个动作
//1. String::~String(pstr);调用三次析构。
//2. operator delete(pstr); 其内部调用free(pstr);
}