⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 mc4.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 5 页
字号:
// see Item E21const UPInt operator+(const UPInt& lhs, const UPInt& rhs);UPInt upi1, upi2;...UPInt upi3 = upi1 + upi2;There are no surprises here. upi1 and upi2 are both UPInt objects, so adding them together just calls operator+ for UPInts.Now consider these statements: upi3 = upi1 + 10;upi3 = 10 + upi2;These statements also succeed. They do so through the creation of temporary objects to convert the integer 10 into UPInts (see Item 19).It is convenient to have compilers perform these kinds of conversions, but the temporary objects created to make the conversions work are a cost we may not wish to bear. Just as most people want government benefits without having to pay for them, most C++ programmers want implicit type conversions without incurring any cost for temporaries. But without the computational equivalent of deficit spending, how can we do it?We can take a step back and recognize that our goal isn't really type conversion, it's being able to make calls to operator+ with a combination of UPInt and int arguments. Implicit type conversion happens to be a means to that end, but let us not confuse means and ends. There is another way to make mixed-type calls to operator+ succeed, and that's to eliminate the need for type conversions in the first place. If we want to be able to add UPInt and int objects, all we have to do is say so. We do it by declaring several functions, each with a different set of parameter types: const UPInt operator+(const UPInt& lhs,      // add UPInt                      const UPInt& rhs);     // and UPIntconst UPInt operator+(const UPInt& lhs,      // add UPInt                      int rhs);              // and intconst UPInt operator+(int lhs,               // add int and                      const UPInt& rhs);     // UPIntUPInt upi1, upi2;...UPInt upi3 = upi1 + upi2;                    // fine, no temporary for                                             // upi1 or upi2upi3 = upi1 + 10;                            // fine, no temporary for                                             // upi1 or 10upi3 = 10 + upi2;                            // fine, no temporary for                                             // 10 or upi2Once you start overloading to eliminate type conversions, you run the risk of getting swept up in the passion of the moment and declaring functions like this: const UPInt operator+(int lhs, int rhs);           // error!The thinking here is reasonable enough. For the types UPInt and int, we want to overload on all possible combinations for operator+. Given the three overloadings above, the only one missing is operator+ taking two int arguments, so we want to add it.Reasonable or not, there are rules to this C++ game, and one of them is that every overloaded operator must take at least one argument of a user-defined type. int isn't a user-defined type, so we can't overload an operator taking only arguments of that type. (If this rule didn't exist, programmers would be able to change the meaning of predefined operations, and that would surely lead to chaos. For example, the attempted overloading of operator+ above would change the meaning of addition on ints. Is that really something we want people to be able to do?)Overloading to avoid temporaries isn't limited to operator functions. For example, in most programs, you'll want to allow a string object everywhere a char* is acceptable, and vice versa. Similarly, if you're using a numerical class like complex (see Item 35), you'll want types like int and double to be valid anywhere a numerical object is. As a result, any function taking arguments of type string, char*, complex, etc., is a reasonable candidate for overloading to eliminate type conversions.Still, it's important to keep the 80-20 rule (see Item 16) in mind. There is no point in implementing a slew of overloaded functions unless you have good reason to believe that it will make a noticeable improvement in the overall efficiency of the programs that use them. Back to Item 21: Overload to avoid implicit type conversionsContinue to Item 23: Consider alternative librariesItem 22: Consider using op= instead of stand-alone op.Most programmers expect that if they can say things like these, x = x + y;                    x = x - y;they can also say things like these: x += y;                       x -= y;If x and y are of a user-defined type, there is no guarantee that this is so. As far as C++ is concerned, there is no relationship between operator+, operator=, and operator+=, so if you want all three operators to exist and to have the expected relationship, you must implement that yourself. Ditto for the operators -, *, /, etc.A good way to ensure that the natural relationship between the assignment version of an operator (e.g., operator+=) and the stand-alone version (e.g., operator+) exists is to implement the latter in terms of the former (see also Item 6). This is easy to do: class Rational {public:  ...  Rational& operator+=(const Rational& rhs);  Rational& operator-=(const Rational& rhs);};// operator+ implemented in terms of operator+=; see// Item E21 for an explanation of why the return value is// const and page 109 for a warning about implementationconst Rational operator+(const Rational& lhs,                         const Rational& rhs){  return Rational(lhs) += rhs;}// operator- implemented in terms of operator -=const Rational operator-(const Rational& lhs,                         const Rational& rhs){  return Rational(lhs) -= rhs;}In this example, operators += and -= are implemented (elsewhere) from scratch, and operator+ and operator- call them to provide their own functionality. With this design, only the assignment versions of these operators need to be maintained. Furthermore, assuming the assignment versions of the operators are in the class's public interface, there is never a need for the stand-alone operators to be friends of the class (see Item E19).If you don't mind putting all stand-alone operators at global scope, you can use templates to eliminate the need to write the stand-alone functions: template<class T>const T operator+(const T& lhs, const T& rhs){  return T(lhs) += rhs;                     // see discussion below}template<class T>const T operator-(const T& lhs, const T& rhs){  return T(lhs) -= rhs;                      // see discussion below}...With these templates, as long as an assignment version of an operator is defined for some type T, the corresponding stand-alone operator will automatically be generated if it's needed.All this is well and good, but so far we have failed to consider the issue of efficiency, and efficiency is, after all, the topic of this chapter. Three aspects of efficiency are worth noting here. The first is that, in general, assignment versions of operators are more efficient than stand-alone versions, because stand-alone versions must typically return a new object, and that costs us the construction and destruction of a temporary (see Items 19 and 20, as well as Item E23). Assignment versions of operators write to their left-hand argument, so there is no need to generate a temporary to hold the operator's return value.The second point is that by offering assignment versions of operators as well as stand-alone versions, you allow clients of your classes to make the difficult trade-off between efficiency and convenience. That is, your clients can decide whether to write their code like this, Rational a, b, c, d, result;...result = a + b + c + d;                      // probably uses 3 temporary                                             // objects, one for each call                                             // to operator+or like this: result = a;                                  // no temporary neededresult += b;                                 // no temporary neededresult += c;                                 // no temporary neededresult += d;                                 // no temporary neededThe former is easier to write, debug, and maintain, and it offers acceptable performance about 80% of the time (see Item 16). The latter is more efficient, and, one supposes, more intuitive for assembly language programmers. By offering both options, you let clients develop and debug code using the easier-to-read stand-alone operators while still reserving the right to replace them with the more efficient assignment versions of the operators. Furthermore, by implementing the stand-alones in terms of the assignment versions, you ensure that when clients switch from one to the other, the semantics of the operations remain constant.The final efficiency observation concerns implementing the stand-alone operators. Look again at the implementation for operator+: template<class T>const T operator+(const T& lhs, const T& rhs){ return T(lhs) += rhs; }The expression T(lhs) is a call to T's copy constructor. It creates a temporary object whose value is the same as that of lhs. This temporary is then used to invoke operator+= with rhs, and the result of that operation is returned from operator+.8 This code seems unnecessarily cryptic. Wouldn't it be better to write it like this? template<class T>const T operator+(const T& lhs, const T& rhs){  T result(lhs);                             // copy lhs into result  return result += rhs;                      // add

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -