C++—特殊成员函数

华电北风吹
天津大学认知计算与应用重点实验室
最后修改日期 2015/11/10

先看一个有问题的类定义:

#include <iostream>
class StringBad
{
private:
    char* str;
    int len;
    static int strcount;
public:
    StringBad();
    StringBad(const char *s);
    ~StringBad();
    friend std::ostream & operator<<(std::ostream & os, const StringBad & sb);
};

类实现代码:

int StringBad::strcount = 0;
StringBad::StringBad()
{
    len = 4;
    str = new char[8];
    strcpy(str, "nullStr");
    ++strcount;
    cout << "StringBad " << strcount << ":" << str << " created." << endl;
}
StringBad::StringBad(const char *s)
{
    len = strlen(s);
    str = new char[len+1];
    strcpy(str, s);
    ++strcount;
    cout << "StringBad " << strcount << ":" << str << " created." << endl;
}
StringBad::~StringBad()
{
    cout << "StringBad " << strcount << ":" << str << " deleted." << endl;
    --strcount;
    delete[] str;
}
ostream & operator<<(ostream & os, const StringBad & sb)
{
    cout << sb.str;
    return os;
}

使用如下的测试函数,会发现好几个析构函数在释放的时候输出乱码(因为指针指向的内容已经被释放)。

void callme1(StringBad & rsb)
{
    cout << "String passed by reference:\n" << " \"" << rsb << "\"\n";
}
void callme2(StringBad sb)
{
    cout << "String passed by value:\n" << " \"" << sb << "\"\n";
}
int main()
{
    using std::endl;
    {
        cout << "Starting an inner block.\n";

        StringBad headline1("China");
        StringBad headline2("Japan");
        StringBad sports("America");
        cout << "headline1: " << headline1 << endl;
        cout << "headline2: " << headline2 << endl;
        cout << "sports: " << sports << endl;
        cout << endl;

        callme1(headline1);
        cout << "headline1: " << headline1 << endl;
        cout << endl;

        callme2(headline2);
        cout << "headline2: " << headline2 << endl;
        cout << endl;

        cout << "Initialize one object to another:\n";
        StringBad sailor = sports;
        cout << "sailor: " << sailor << endl;
        cout << endl;

        cout << "Assign one object to another:\n";
        StringBad knot;
        knot = headline1;
        cout << "knot: " << knot << endl;
        cout << endl;

        cout << "Exiting the block. Destruct function start to execute.\n";
        cout << endl;
    }
    cout << "End of main()\n";
    std::cin.get();
    return 0;
}

会发现sb.str指针指向的内容已经被释放了。下面主要讨论一下怎么解决这个问题。

C++包含五种特殊成员函数,分别是默认构造函数,默认析构函数,复制构造函数,复制运算符,地址运算符。C++11还提供了移动构造函数和移动赋值元算符。

1、默认构造函数
如果类声明没有提供任何构造函数,C++将创建默认构造函数。默认的构造函数不接受任何参数,也不执行任何操作(创建对象时里面的值是未知的)。
如果定义了构造函数,C++将不会定义默认构造函数。这里需要注意如果希望创建对象时不显式对他进行初始化,则必须显式定义默认构造函数。
例如,若想使用下面的语句,必须在构造函数声明默认构造函数。

StringBad knot; //必须有构造函数 StringBad()

带参数的构造函数也可以使默认构造函数,但只能有一个默认的构造函数。例如下面的构造函数定义将会引发编译错误。

StringBad(){};
StringBad(const char *s="second default construct function"){};

因为如果出现StringBad sb;将会与这两个默认构造函数都匹配。

2、默认析构函数
如果没有定义析构函数,C++默认定义一个不进行任何操作的析构函数。

3、复制构造函数
类的复制构造函数原型如下

Class_name(const Class_name &);

有一下四种情况调用类的复制构造函数。假设motto是一个StringBad对象:

StringBad ditto(motto); StringBad metoo=motto; StringBad also=StringBad(motto); StringBad *pStringBad=new StringBad(motto);

C++默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。如果成员本身就是类对象,则将使用这个类的复制构造函数来复制类成员对象。所以如果类中包含了使用new初始化的指针成员的时候应当定义一个复制构造函数,以复制指针指向的数据,而不是指针,也就是所说的深度复制。而浅复制只是复制指针值。
因此,上面的StringBad类应该定义自己的如下复制构造函数

StringBad::StringBad(const StringBad & sb)
{
    len = sb.len;
    str = new char[len + 1];
    strcpy(str, sb.str);
    ++strcount;
    cout << "StringBad " << strcount << ":" << str << " created." << endl;
}

4、赋值运算符
进行上述修改后,可以解决大部分问题,运行程序发现,执行最后一个析构函数的时候,还是出现了指针指向内容被释放的情况。问题出在下面两行代码:

StringBad knot; knot = headline1;

第一行执行无参数的默认构造函数。第二行执行默认的赋值运算符。有了上面的经验不难猜测出这里的问题是赋值运算符的隐含实现是对成员逐个复制。
只需要重载赋值运算符进行深度复制即可。

StringBad & StringBad::operator=(const StringBad &sb)
{
    if (this == &sb)
        return *this;
    this->len = sb.len;
    delete[] this->str;
    this->str = new char[this->len + 1];
    strcpy(this->str, sb.str);
    return *this;
}

至此,已经讲一个StringBad类修复好了。
地址运算符、移动构造函数、移动赋值元算符部分等待后续补充。

你可能感兴趣的:(C++—特殊成员函数)