8.1.3 析构函数与动态内存分配(1)
程序中经常需要为类的数据成员动态分配内存。可以在构造函数中使用new操作符来为对象成员分配空间。在这种情况下,必须提供适当的析构函数,在不再需要该对象时释放内存(或者使用智能指针,参见第10章)。下面首先定义一个简单的类,以进行这样的练习。
假设定义一个类,其中每个对象都是描述性的消息(如文本字符串)。这个类应该尽可能高效地利用内存,因此不能将数据成员定义成足以容纳所需最大长度字符串的char数组。应该在创建对象时在空闲存储器中为消息分配内存。类定义如下所示:
- //Listing 08_02_1
- class CMessage
- {
- private:
- char* pmessage; // Pointer to object text string
- public:
- // Function to display a message
- void ShowIt() const
- {
- cout << endl << pmessage;
- }
- // Constructor definition
- CMessage(const char* text = "Default message")
- {
- pmessage = new char[strlen(text) + 1]; // Allocate space for text
- strcpy_s(pmessage, strlen(text) + 1, text); // Copy text to new memory
- }
- ~CMessage(); // Destructor prototype
- };
该类仅定义了一个数据成员pmessage,该成员是一个指向文本串的指针。这是在类的private部分定义的,因此不能从类外部访问。
在public部分,ShowIt()函数为CMessage对象输出字符串。还定义了构造函数,并添加了该类的析构函数~CMessage()的原型,我们很快就会讨论它。
类的构造函数要求实参是字符串,但如果不传递任何实参,则它使用为形参指定的默认字符串。构造函数使用库函数strlen(),获得字符串实参的长度(不包括终止空字符NULL)。为了使构造函数能够使用strlen()库函数,程序中必须有嵌入cstring头文件的#include语句。
注意:cstring头文件声明了来自C运行库中的函数,它们不在名称空间中定义,所以函数名可以不使用名称空间名来限定。因为这些函数也是C++(www.cppentry.com)标准库的一部分,所以这些函数名也在std名称空间中定义。因此可以用std限定它们。
通过将strlen()函数返回的数值加1,构造函数即可求出在空闲存储器中存储该字符串所需要的内存字节数。
注意:当然,如果内存分配失败,则将抛出异常并且终止程序。如果我们希望管理此类故障,以便程序顺利运行,那么应该在构造函数代码中捕获此类异常(见第6章关于处理内存不足状况的信息)。
之后,我们使用也是在cstring头文件中声明的strcpy_s()库函数,将给构造函数提供的字符串实参复制到为字符串分配的内存中。strcpy_s()函数将第三个指针实参指定的字符串复制到第一个指针实参包含的地址中。第二个实参指定目标位置的长度。
现在需要编写类的析构函数,以释放为消息分配的内存。如果不给该类提供析构函数,那么程序将无法释放为类对象分配的内存。如果按照现状在程序中使用这个类创建大量的CMessage对象,那么将逐渐耗尽空闲存储器,直至程序失败为止。在不容易发现此类问题的环境中,却很容易出现上述现象。例如,如果要在一个被程序多次调用的函数中创建临时的CMessage对象,则可能认为在从函数返回时销毁该对象。当然,这种看法是正确的,只是没有释放空闲存储器中的内存。因此,每调用一次该函数,就有更多的空闲存储器内存被抛弃的CMessage对象占用。