在构造函数中使用new时应注意的事项
使用new初始化对象的指针成员时必须特别小心。
● 如果在构造函数中使用new来初始化指针成员,则应该在析构函数中使用delete。
● new和delete必须相互兼容。new对应于delete,new[]对应于delete[]。
● 如果有多个析构函数,则必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构函数,所有的构造函数都必须与它兼容。然而,可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空,这是因为delete(无论是带中括号还是不带中括号)可以用于空指针。
● 应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。
String::String(const String &st){num_strings++; //如果需要,则处理静态成员更新len = st.len; //复制字符串相同长度str = new char[len + 1]; //分配空间strcpy(str, st.str); //复制字符串给新的地址}
复制构造函数应分配足够的空间来存储复制的数据,并复制数据,而不仅仅是数据的地址,还应该更新所有受影响的静态类成员。
● 应当定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象。
String & String::operator=(const String &st){if (this == &st) //分配对象给它本身return *this;delete[] str; //释放以前的字符串len = st.len; str = new char[len + 1]; //获得空间给新的字符串strcpy(str, st.str); //复制字符串return *this; //返回调用对象的引用}
检查自我赋值的情况,释放成员指针以前指向的内存,复制数据而不仅仅是数据的地址,并返回一个指向调用对象的引用。
应该和不应该
下面的摘要包含了两个不正确的示例,和一个良好的构造函数示例:
String::String(){str = "default string"; //没有new[]len = std::strlen(str);}String::String(const char* s){len = std::strlen(s);str = new char; //没有[]std::strcpy(str, s); //没有申请空间}String::String(const String &st){len = st.len;str = new char[len+1]; //分片空间std::strcpy(str, st.str); //拷贝值}
第一个构造函数没有使用new来初始化str。对默认对象调用析构函数时,析构函数使用delete来释放str。对不是使用new初始化的指针使用delete时,结果将是不确定的,并可能有害的。可将该构造函数修改为下面的任何一种形式:
String::String(){len = 0;str = new char[1]; //利用new和[]str[0] = '\0';}String::String(){len = 0;str = 0; //C++11 str=nullptr}String::String(){static const char* s = "C++"; //初始化仅一次len = std::strlen(s);str = new char[len+1]; //利用new和[]std::strcpy(str, s);}
第二个构造函数使用了new,但分配的内存量不正确。因此,new返回的内存块只能保存一个字符。试图将过长的字符串复制到该内存单元中,将导致内存问题。另外,这里使用的new不带中括号,这与另一个构造函数的正确格式不一致。
第三个构造函数正确。
最后,下面的析构函数无法与前面的构造函数正常地协同工作:
String::~String(){delete str; //应该delete[]str;}
该析构函数未能正确使用delete。由于构造函数创建的是一个字符数组,因此析构函数应该删除一个数组。
包含类成员的类的逐成员复制
假设类成员的类型为String类或标准string类:
class Magazine{private:String title;string publisher;...};
String和string都使用动态内存分配,但这不意味着需要为Magazine类编写复制构造函数和赋值运算符。
默认的逐成员复制和赋值行为有一定的智能。如果将一个Magazine对象复制或赋值给另一个Magazine对象,逐成员复制将使用成员类型定义的复制构造函数和赋值运算符。
复制成员title时,将使用String的复制构造函数,而将成员title赋给另一个Magazine对象时,将使用String的赋值运算符,依次类推。然后,如果Magazine类因其他成员需要定义复制构造函数和赋值运算符,情况将更复杂:这种情况,这些函数必须显式调用String和string的复制构造函数和赋值运算符。