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

📄 ec4.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 5 页
字号:
    lengthIsValid = true;         // also fine  }  return dataLength;}mutable is a wonderful solution to the bitwise-constness-is-not-quite-what-I-had-in-mind problem, but it was added to C++ relatively late in the standardization process, so your compilers may not support it yet. If that's the case, you must descend into the dark recesses of C++, where life is cheap and constness may be cast away.Inside a member function of class C, the this pointer behaves as if it had been declared as follows: C * const this;                        // for non-const member                                       // functionsconst C * const this;                  // for const member                                       // functionsThat being the case, all you have to do to make the problematic version of String::length (i.e., the one you could fix with mutable if your compilers supported it) valid for both const and non-const objects is to change the type of this from const C * const to C * const. You can't do that directly, but you can fake it by initializing a local pointer to point to the same object as this does. Then you can access the members you want to modify through the local pointer: size_t String::length() const{  // make a local version of this that's  // not a pointer-to-const  String * const localThis =    const_cast<String * const>(this);  if (!lengthIsValid) {    localThis->dataLength = strlen(data);    localThis->lengthIsValid = true;  }  return dataLength;}Pretty this ain't, but sometimes a programmer's just gotta do what a programmer's gotta do.Unless, of course, it's not guaranteed to work, and sometimes the old cast-away-constness trick isn't. In particular, if the object this points to is truly const, i.e., was declared const at its point of definition, the results of casting away its constness are undefined. If you want to cast away constness in one of your member functions, you'd best be sure that the object you're doing the casting on wasn't originally defined to be const.There is one other time when casting away constness may be both useful and safe. That's when you have a const object you want to pass to a function taking a non-const parameter, and you know the parameter won't be modified inside the function. The second condition is important, because it is always safe to cast away the constness of an object that will only be read not written even if that object was originally defined to be const.For example, some libraries have been known to incorrectly declare the strlen function as follows: size_t strlen(char *s);Certainly strlen isn't going to modify what s points to at least not the strlen I grew up with. Because of this declaration, however, it would be invalid to call it on pointers of type const char *. To get around the problem, you can safely cast away the constness of such pointers when you pass them to strlen: const char *klingonGreeting = "nuqneH";        // "nuqneH" is                                               // "Hello" in                                               // Klingonsize_t length =  strlen(const_cast<char*>(klingonGreeting));Don't get cavalier about this, though. It is guaranteed to work only if the function being called, strlen in this case, doesn't try to modify what its parameter points to. Back to Item 21: Use const whenever possible.Continue to Item 23: Don't try to return a reference when you must return an object.Item 22: Prefer pass-by-reference to pass-by-value.In C, everything is passed by value, and C++ honors this heritage by adopting the pass-by-value convention as its default. Unless you specify otherwise, function parameters are initialized with copies of the actual arguments, and function callers get back a copy of the value returned by the function.As I pointed out in the Introduction to this book, the meaning of passing an object by value is defined by the copy constructor of that object's class. This can make pass-by-value an extremely expensive operation. For example, consider the following (rather contrived) class hierarchy: class Person {public:  Person();                         // parameters omitted for                                    // simplicity  ~Person();  ...private:  string name, address;};class Student: public Person {public:  Student();                        // parameters omitted for                                    // simplicity  ~Student();  ...private:  string schoolName, schoolAddress;};Now consider a simple function returnStudent that takes a Student argument (by value) and immediately returns it (also by value), plus a call to that function: Student returnStudent(Student s) { return s; }Student plato;                      // Plato studied under                                    // SocratesreturnStudent(plato);               // call returnStudentWhat happens during the course of this innocuous-looking function call?The simple explanation is this: the Student copy constructor is called to initialize s with plato. Then the Student copy constructor is called again to initialize the object returned by the function with s. Next, the destructor is called for s. Finally, the destructor is called for the object returned by returnStudent. So the cost of this do-nothing function is two calls to the Student copy constructor and two calls to the Student destructor.But wait, there's more! A Student object has two string objects within it, so every time you construct a Student object you must also construct two string objects. A Student object also inherits from a Person object, so every time you construct a Student object you must also construct a Person object. A Person object has two additional string objects inside it, so each Person construction also entails two more string constructions. The end result is that passing a Student object by value leads to one call to the Student copy constructor, one call to the Person copy constructor, and four calls to the string copy constructor. When the copy of the Student object is destroyed, each constructor call is matched by a destructor call, so the overall cost of passing a Student by value is six constructors and six destructors. Because the function returnStudent uses pass-by-value twice (once for the parameter, once for the return value), the complete cost of a call to that function is twelve constructors and twelve destructors!In fairness to the C++ compiler-writers of the world, this is a worst-case scenario. Compilers are allowed to eliminate some of these calls to copy constructors. (The C++ standard see Item 50 describes the precise conditions under which they are allowed to perform this kind of magic, and Item M20 gives examples). Some compilers take advantage of this license to optimize. Until such optimizations become ubiquitous, however, you've got to be wary of the cost of passing objects by value.To avoid this potentially exorbitant cost, you need to pass things not by value, but by reference: const Student& returnStudent(const Student& s){ return s; }This is much more efficient: no constructors or destructors are called, because no new objects are being created.Passing parameters by reference has another advantage: it avoids what is sometimes called the "slicing problem." When a derived class object is passed as a base class object, all the specialized features that make it behave like a derived class object are "sliced" off, and you're left with a simple base class object. This is almost never what you want. For example, suppose you're working on a set of classes for implementing a graphical window system: class Window {public:  string name() const;             // return name of window  virtual void display() const;    // draw window and contents};class WindowWithScrollBars: public Window {public:  virtual void display() const;};All Window objects have a name, which you can get at through the name function, and all windows can be displayed, which you can bring about by invoking the display function. The fact that display is virtual tells you that the way in which simple base class Window objects are displayed is apt to differ from the way in which the fancy, high-priced WindowWithScrollBars objects are displayed (see Items 36, 37, and M33).Now suppose you'd like to write a function to print out a window's name and then display the window. Here's the wrong way to write such a function: // a function that suffers from the slicing problemvoid printNameAndDisplay(Window w){  cout << w.name();  w.display();}Consider what happens when you call this function with a WindowWithScrollBars object: WindowWithScrollBars wwsb;printNameAndDisplay(wwsb);The parameter w will be constructed it's passed by value, remember? as a Window object, and all the specialized information that made wwsb act like a WindowWithScrollBars object will be sliced off. Inside printNameAndDisplay, w will always act like an object of class Window (because it is an object of class Window), regardless of the type of object that is passed to the function. In particular, the call to display inside printNameAndDisplay will always call Window::display, never WindowWithScrollBars::display.The way around the slicing problem is to pass w by reference: // a function that doesn't suffer from the slicing problemvoid printNameAndDisplay(const Window& w){  cout << w.name();  w.display();}Now w will act like whatever kind of window is actually passed in. To emphasize that w isn't modified by this function even though it's passed by reference, you've followed the advice of Item 21 and carefully declared it to be const; how good of you.Passing by reference is a wonderful thing, but it leads to certain complications of its own, the most notorious of which is aliasing, a topic that is discussed in Item 17. In addition, it's important to recognize that you sometimes can't pass things by reference; see Item 23. Finally, the brutal fact of the matter is that references are almost always implemented as pointers, so passing something by reference usually means really passing a pointer. As a result, if you have a small object an int, for example it may actually be more efficient to pass it by value than to pass it by reference. Back to Item 22: Prefer pass-by-reference to pass-by-value.Continue to Item 24: Choose carefully between function overloading and parameter defaulting.Item 23: Don't try to return a reference when you must return an object.It is said that Albert Einstein once offered this advice: make things as simple as possible, but no simpler. The C++ analogue might well be to make things as efficient as possible, but no more efficient.Once programmers grasp the efficiency implications of pass-by-value for objects (see Item 22), they become crusaders, determined to root out the evil of pass-by-value wherever it may hide. Unrelenting in their pursuit of pass-by-reference purity, they invariably make a fatal mistake: they start to pass references to objects that don't exist. This is not a good thing.Consider a class for representing rational numbers, including a friend function (see Item 19) for multiplying two rationals together: class Rational {public:  Rational(int numerator = 0, int denominator = 1);  ...private:  int n, d;              // numerator and denominatorfriend  const Rational                      // see Item 21 for why    operator*(const Rational& lhs,    // the return value is              const Rational& rhs)    // const};inline const Rational operator*(const Rational& lhs,                                const Rational& rhs){  return Rational(lhs.n * rhs.n, lhs.d * rhs.d);

⌨️ 快捷键说明

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