📄 mc6.htm
字号:
};Only the assignment operators are shown here, but that's more than enough to keep us busy for a while. Consider this code: Lizard liz1;Lizard liz2;Animal *pAnimal1 = &liz1;Animal *pAnimal2 = &liz2;...*pAnimal1 = *pAnimal2;There are two problems here. First, the assignment operator invoked on the last line is that of the Animal class, even though the objects involved are of type Lizard. As a result, only the Animal part of liz1 will be modified. This is a partial assignment. After the assignment, liz1's Animal members have the values they got from liz2, but liz1's Lizard members remain unchanged.The second problem is that real programmers write code like this. It's not uncommon to make assignments to objects through pointers, especially for experienced C programmers who have moved to C++. That being the case, we'd like to make the assignment behave in a more reasonable fashion. As Item 32 points out, our classes should be easy to use correctly and difficult to use incorrectly, and the classes in the hierarchy above are easy to use incorrectly.One approach to the problem is to make the assignment operators virtual. If Animal::operator= were virtual, the assignment would invoke the Lizard assignment operator, which is certainly the correct one to call. However, look what happens if we declare the assignment operators virtual: class Animal {public: virtual Animal& operator=(const Animal& rhs); ...};class Lizard: public Animal {public: virtual Lizard& operator=(const Animal& rhs); ...};class Chicken: public Animal {public: virtual Chicken& operator=(const Animal& rhs); ...};Due to relatively recent changes to the language, we can customize the return value of the assignment operators so that each returns a reference to the correct class, but the rules of C++ force us to declare identical parameter types for a virtual function in every class in which it is declared. That means the assignment operator for the Lizard and Chicken classes must be prepared to accept any kind of Animal object on the right-hand side of an assignment. That, in turn, means we have to confront the fact that code like the following is legal: Lizard liz;Chicken chick;Animal *pAnimal1 = &liz;Animal *pAnimal2 = &chick;...*pAnimal1 = *pAnimal2; // assign a chicken to // a lizard!This is a mixed-type assignment: a Lizard is on the left and a Chicken is on the right. Mixed-type assignments aren't usually a problem in C++, because the language's strong typing generally renders them illegal. By making Animal's assignment operator virtual, however, we opened the door to such mixed-type operations.This puts us in a difficult position. We'd like to allow same-type assignments through pointers, but we'd like to forbid mixed-type assignments through those same pointers. In other words, we want to allow this, Animal *pAnimal1 = &liz1;Animal *pAnimal2 = &liz2;...*pAnimal1 = *pAnimal2; // assign a lizard to a lizardbut we want to prohibit this: Animal *pAnimal1 = &liz;Animal *pAnimal2 = &chick;...*pAnimal1 = *pAnimal2; // assign a chicken to a lizardDistinctions such as these can be made only at runtime, because sometimes assigning *pAnimal2 to *pAnimal1 is valid, sometimes it's not. We thus enter the murky world of type-based runtime errors. In particular, we need to signal an error inside operator= if we're faced with a mixed-type assignment, but if the types are the same, we want to perform the assignment in the usual fashion.We can use a dynamic_cast (see Item 2) to implement this behavior. Here's how to do it for Lizard's assignment operator: Lizard& Lizard::operator=(const Animal& rhs){ // make sure rhs is really a lizard const Lizard& rhs_liz = dynamic_cast<const Lizard&>(rhs); proceed with a normal assignment of rhs_liz to *this;}This function assigns rhs to *this only if rhs is really a Lizard. If it's not, the function propagates the bad_cast exception that dynamic_cast throws when the cast fails. (Actually, the type of the exception is std::bad_cast, because the components of the standard library, including the exceptions thrown by the standard components, are in the namespace std. For an overview of the standard library, see Item E49 and Item 35.)Even without worrying about exceptions, this function seems needlessly complicated and expensive the dynamic_cast must consult a type_info structure; see Item 24 in the common case where one Lizard object is assigned to another: Lizard liz1, liz2;...liz1 = liz2; // no need to perform a // dynamic_cast: this // assignment must be validWe can handle this case without paying for the complexity or cost of a dynamic_cast by adding to Lizard the conventional assignment operator: class Lizard: public Animal {public: virtual Lizard& operator=(const Animal& rhs); Lizard& operator=(const Lizard& rhs); // add this ...};Lizard liz1, liz2;...liz1 = liz2; // calls operator= taking // a const Lizard&Animal *pAnimal1 = &liz1;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -