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

📄 mc4.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 5 页
字号:
     << " occurrences of the character " << c     << " in " << buffer << endl;Look at the call to countChar. The first argument passed is a char array, but the corresponding function parameter is of type const string&. This call can succeed only if the type mismatch can be eliminated, and your compilers will be happy to eliminate it by creating a temporary object of type string. That temporary object is initialized by calling the string constructor with buffer as its argument. The str parameter of countChar is then bound to this temporary string object. When countChar returns, the temporary object is automatically destroyed.Conversions such as these are convenient (though dangerous see Item 5), but from an efficiency point of view, the construction and destruction of a temporary string object is an unnecessary expense. There are two general ways to eliminate it. One is to redesign your code so conversions like these can't take place. That strategy is examined in Item 5. An alternative tack is to modify your software so that the conversions are unnecessary. Item 21 describes how you can do that.These conversions occur only when passing objects by value or when passing to a reference-to-const parameter. They do not occur when passing an object to a reference-to-non-const parameter. Consider this function: void uppercasify(string& str);               // changes all chars in                                             // str to upper caseIn the character-counting example, a char array could be successfully passed to countChar, but here, trying to call uppercasify with a char array fails: char subtleBookPlug[] = "Effective C++";uppercasify(subtleBookPlug);                // error!No temporary is created to make the call succeed. Why not?Suppose a temporary were created. Then the temporary would be passed to uppercasify, which would modify the temporary so its characters were in upper case. But the actual argument to the function call subtleBookPlug would not be affected; only the temporary string object generated from subtleBookPlug would be changed. Surely this is not what the programmer intended. That programmer passed subtleBookPlug to uppercasify, and that programmer expected subtleBookPlug to be modified. Implicit type conversion for references-to-non-const objects, then, would allow temporary objects to be changed when programmers expected non-temporary objects to be modified. That's why the language prohibits the generation of temporaries for non-const reference parameters. Reference-to-const parameters don't suffer from this problem, because such parameters, by virtue of being const, can't be changed.The second set of circumstances under which temporary objects are created is when a function returns an object. For instance, operator+ must return an object that represents the sum of its operands (see Item E23). Given a type Number, for example, operator+ for that type would be declared like this: const Number operator+(const Number& lhs,                       const Number& rhs);The return value of this function is a temporary, because it has no name: it's just the function's return value. You must pay to construct and destruct this object each time you call operator+. (For an explanation of why the return value is const, see Item E21.)As usual, you don't want to incur this cost. For this particular function, you can avoid paying by switching to a similar function, operator+=; Item 22 tells you about this transformation. For most functions that return objects, however, switching to a different function is not an option and there is no way to avoid the construction and destruction of the return value. At least, there's no way to avoid it conceptually. Between concept and reality, however, lies a murky zone called optimization, and sometimes you can write your object-returning functions in a way that allows your compilers to optimize temporary objects out of existence. Of these optimizations, the most common and useful is the return value optimization, which is the subject of Item 20.The bottom line is that temporary objects can be costly, so you want to eliminate them whenever you can. More important than this, however, is to train yourself to look for places where temporary objects may be created. Anytime you see a reference-to-const parameter, the possibility exists that a temporary will be created to bind to that parameter. Anytime you see a function returning an object, a temporary will be created (and later destroyed). Learn to look for such constructs, and your insight into the cost of "behind the scenes" compiler actions will markedly improve. Back to Item 19: Understand the origin of temporary objectsContinue to Item 21: Overload to avoid implicit type conversionsItem 20: Facilitate the return value optimization.A function that returns an object is frustrating to efficiency aficionados, because the by-value return, including the constructor and destructor calls it implies (see Item 19), cannot be eliminated. The problem is simple: a function either has to return an object in order to offer correct behavior or it doesn't. If it does, there's no way to get rid of the object being returned. Period.Consider the operator* function for rational numbers: class Rational {public:  Rational(int numerator = 0, int denominator = 1);  ...  int numerator() const;  int denominator() const;};// For an explanation of why the return value is const,// see Item 6const Rational operator*(const Rational& lhs,                         const Rational& rhs);Without even looking at the code for operator*, we know it must return an object, because it returns the product of two arbitrary numbers. These are arbitrary numbers. How can operator* possibly avoid creating a new object to hold their product? It can't, so it must create a new object and return it. C++ programmers have nevertheless expended Herculean efforts in a search for the legendary elimination of the by-value return (see Items E23 and E31).Sometimes people return pointers, which leads to this syntactic travesty: // an unreasonable way to avoid returning an objectconst Rational * operator*(const Rational& lhs,                           const Rational& rhs);Rational a = 10;Rational b(1, 2);Rational c = *(a * b);                       // Does this look "natural"                                             // to you?It also raises a question. Should the caller delete the pointer returned by the function? The answer is usually yes, and that usually leads to resource leaks.Other developers return references. That yields an acceptable syntax, // a dangerous (and incorrect) way to avoid returning// an objectconst Rational& operator*(const Rational& lhs,                          const Rational& rhs);Rational a = 10;Rational b(1, 2);Rational c = a * b;                          // looks perfectly reasonablebut such functions can't be implemented in a way that behaves correctly. A common attempt looks like this: // another dangerous (and incorrect) way to avoid// returning an objectconst Rational& operator*(const Rational& lhs,                          const Rational& rhs){  Rational result(lhs.numerator() * rhs.numerator(),                  lhs.denominator() * rhs.denominator());  return result;}This function returns a reference to an object that no longer exists. In particular, it returns a reference to the local object result, but result is automatically destroyed when operator* is exited. Returning a reference to an object that's been destroyed is hardly useful.Trust me on this: some functions (operator* among them) just have to return objects. That's the way it is. Don't fight it. You can't win.That is, you can't win in your effort to eliminate by-value returns from functions that require them. But that's the wrong war to wage. From an efficiency point of view, you shouldn't care that a function returns an object, you should only care about the cost of that object. What you need to do is channel your efforts into finding a way to reduce the cost of returned objects, not to eliminate the objects themselves (which we now recognize is a futile quest). If no cost is associated with such objects, who cares how many get created?It is frequently possible to write functions that return objects in such a way that compilers can eliminate the cost of the temporaries. The trick is to return constructor arguments instead of objects, and you can do it like this: // an efficient and correct way to implement a// function that returns an objectconst Rational operator*(const Rational& lhs,                         const Rational& rhs){  return Rational(lhs.numerator() * rhs.numerator(),                  lhs.denominator() * rhs.denominator());}Look closely at the expression being returned. It looks like you're calling a Rational constructor, and in fact you are. You're creating a temporary Rational object through this expression, Rational(lhs.numerator() * rhs.numerator(),         lhs.denominator() * rhs.denominator());and it is this temporary object the function is copying for its return value.This business of returning constructor arguments instead of local objects doesn't appear to have bought you a lot, because you still have to pay for the construction and destruction of the temporary created inside the function, and you still have to pay for the construction and destruction of the object the function returns. But you have gained something. The rules for C++ allow compilers to optimize temporary objects out of existence. As a result, if you call operator* in a context like this, Rational a = 10;Rational b(1, 2);Rational c = a * b;                          // operator* is called hereyour compilers are allowed to eliminate both the temporary inside operator* and the temporary returned by operator*. They can construct the object defined by the return expression inside the memory allotted for the object c. If your compilers do this, the total cost of temporary objects as a result of your calling operator* is zero: no temporaries are created. Instead, you pay for only one constructor call the one to create c. Furthermore, you can't do any better than this, because c is a named object, and named objects can't be eliminated (see also Item 22).7 You can, however, eliminate the overhead of the call to operator* by declaring that function inline (but first see Item E33): // the most efficient way to write a function returning// an objectinline const Rational operator*(const Rational& lhs,                                const Rational& rhs){  return Rational(lhs.numerator() * rhs.numerator(),                  lhs.denominator() * rhs.denominator());}"Yeah, yeah," you mutter, "optimization, schmoptimization. Who cares what compilers can do? I want to know what they do do. Does any of this nonsense work with real compilers?" It does. This particular optimization eliminating a local temporary by using a function's return location (and possibly replacing that with an object at the function's call site) is both well-known and commonly implemented. It even has a name: the return value optimization. In fact, the existence of a name for this optimization may explain why it's so widely available. Programmers looking for a C++ compiler can ask vendors whether the return value optimization is implemented. If one vendor says yes and another says "The what?," the first vendor has a notable competitive advantage. Ah, capitalism. Sometimes you just gotta love it. Back to Item 20: Facilitate the return value optimizationContinue to Item 22: Consider using op= instead of stand-alone opItem 21: Overload to avoid implicit type conversions.Here's some code that looks nothing if not eminently reasonable: class UPInt {                                 // class for unlimitedpublic:                                       // precision integers  UPInt();  UPInt(int value);  ...};// For an explanation of why the return value is const,

⌨️ 快捷键说明

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