8.4.3 重载赋值运算符(2)
现在的情况是,从函数operator=()返回的对象用来调用operator=()函数。如果返回类型仅仅是CMessage,则该语句是不合法的,因为实际返回的是原始对象的临时副本,它是rvalue,编译器不允许使用rvalue调用成员函数。确保此类语句能够正确编译和工作的唯一方法是返回一个引用,它是lvalue,因此如果希望实现使用赋值运算符处理类对象的灵活性,则唯一可能的返回类型是CMessage&。
注意,C++(www.cppentry.com)语言对赋值运算符的形参和返回类型没有任何限制,但如果希望自己的赋值运算符函数支持常规的赋值用法,那么以刚才描述的方式声明赋值运算符就具有现实意义。
第二点微妙之处是,两个对象都已经拥有为字符串分配的内存,因此赋值运算符函数首先要删除分配给第一个对象的内存,然后重新分配足够的内存,以容纳属于第二个对象的字符串。做完这件事之后,就可以将来自第二个对象的字符串复制到第一个对象现在拥有的新内存中。
该运算符函数中仍然存在缺点。如果写出下面这条语句,那么将发生什么事情呢?
- motto1motto1 = motto1;
显然,我们不会直接那样做,但此类现象很容易隐藏在指针的背后,就像在下面的语句中的那样:
- Motto1 = *pMessage;
如果指针pMessage指向motto1,那么实质上这是前面那条赋值语句。这种情况下,目前的赋值运算符函数将释放供motto1使用的内存,然后基于已删除的字符串的长度另外分配一些内存,并试图复制当时很可能已经被破坏的旧内存。通过在函数的开头检查左右操作数是否相同,就可以修正上述问题,因此现在operator=()函数的定义将如下所示:
- // Overloaded assignment operator for CMessage objects
- CMessage& operator=(const CMessage& aMess)
- {
- if(this != &aMess) // Check addresses are not equal
- {
- // Release memory for 1st operand
- delete[] pmessage;
- pmessage = new char[strlen(aMess.pmessage) + 1];
- // Copy 2nd operand string to 1st
- strcpy_s(this->pmessage, strlen(aMess.pmessage)+1, aMess.pmessage);
- }
- // Return a reference to 1st operand
- return *this;
- }
试一试:重载赋值运算符
下面将完整的operator=()函数代码放在可运行的示例中,同时还向CMessage类中添加一个Reset()成员函数。该函数的作用仅是将消息重新设置为星号字符串。
- // Ex8_05.cpp
- // Overloaded assignment operator working well
- #include <iostream>
- #include <cstring>
- using std::cout;
- using std::endl;
- class CMessage
- {
- private:
- char* pmessage; // Pointer to object text string
- public:
- // Function to display a message
- void ShowIt() const
- {
- cout << endl << pmessage;
- }
- //Function to reset a message to *
- void Reset()
- {
- char* temp = pmessage;
- while(*temp)
- *(temp++) = '*';
- }
- // Overloaded assignment operator for CMessage objects
- CMessage& operator=(const CMessage& aMess)
- {
- if(this != &aMess) // Check addresses are not equal
- {
- // Release memory for 1st operand
- delete[] pmessage;
- pmessage = new char[strlen(aMess.pmessage) + 1];
- // Copy 2nd operand string to 1st
- strcpy_s(this->pmessage, strlen(aMess.pmessage) + 1, aMess.pmessage);
- }
- // Return a reference to 1st operand
- return *this;
- }
- // 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
- }
- // Copy constructor definition
- CMessage(const CMessage& aMess)
- {
- size_t len = strlen(aMess.pmessage)+1;
- pmessage = new char[len];
- strcpy_s(pmessage, len, aMess.pmessage);
- }
- // Destructor to free memory allocated by new
- ~CMessage()
- {
- cout << "Destructor called." // Just to track what happens
- << endl;
- delete[] pmessage; // Free memory assigned to pointer
- }
- };
- int main()
- {
- CMessage motto1("The devil takes care of his own");
- CMessage motto2;
- cout << "motto2 contains - ";
- motto2.ShowIt();
- cout << endl;
- motto2 = motto1; // Use new assignment operator
- cout << "motto2 contains - ";
- motto2.ShowIt();
- cout << endl;
- motto1.Reset(); // Setting motto1 to * doesn't
- // affect motto2
- cout << "motto1 now contains - ";
- motto1.ShowIt();
- cout << endl;
- cout << "motto2 still contains - ";
- motto2.ShowIt();
- cout << endl;
- return 0;
- }