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

📄 mc5.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 5 页
字号:
 More Effective C++ | Chapter 5: Techniques Back to Item 24: Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTIContinue to Item 25: Virtualizing constructors and non-member functions.TechniquesMost of this book is concerned with programming guidelines. Such guidelines are important, but no programmer lives by guidelines alone. According to the old TV show Felix the Cat, "Whenever he gets in a fix, he reaches into his bag of tricks." Well, if a cartoon character can have a bag of tricks, so too can C++ programmers. Think of this chapter as a starter set for your bag of tricks.Some problems crop up repeatedly when designing C++ software. How can you make constructors and non-member functions act like virtual functions? How can you limit the number of instances of a class? How can you prevent objects from being created on the heap? How can you guarantee that they will be created there? How can you create objects that automatically perform some actions anytime some other class's member functions are called? How can you have different objects share data structures while giving clients the illusion that each has its own copy? How can you distinguish between read and write usage of operator[]? How can you create a virtual function whose behavior depends on the dynamic types of more than one object?All these questions (and more) are answered in this chapter, in which I describe proven solutions to problems commonly encountered by C++ programmers. I call such solutions techniques, but they're also known as idioms and, when documented in a stylized fashion, patterns. Regardless of what you call them, the information that follows will serve you well as you engage in the day-to-day skirmishes of practical software development. It should also convince you that no matter what you want to do, there is almost certainly a way to do it in C++. Back to TechniquesContinue to Item 26: Limiting the number of objects of a classItem 25: Virtualizing constructors and non-member functions.On the face of it, it doesn't make much sense to talk about "virtual constructors." You call a virtual function to achieve type-specific behavior when you have a pointer or reference to an object but you don't know what the real type of the object is. You call a constructor only when you don't yet have an object but you know exactly what type you'd like to have. How, then, can one talk of virtual constructors?It's easy. Though virtual constructors may seem nonsensical, they are remarkably useful. (If you think nonsensical ideas are never useful, how do you explain the success of modern physics?) For example, suppose you write applications for working with newsletters, where a newsletter consists of components that are either textual or graphical. You might organize things this way: class NLComponent {               // abstract base class forpublic:                           // newsletter components  ...                             // contains at least one};                                // pure virtual functionclass TextBlock: public NLComponent {public:  ...                             // contains no pure virtual};                                // functionsclass Graphic: public NLComponent {public:  ...                             // contains no pure virtual};                                // functionsclass NewsLetter {                // a newsletter objectpublic:                           // consists of a list of  ...                             // NLComponent objectsprivate:  list<NLComponent*> components;};The classes relate in this way:The list class used inside NewsLetter is part of the Standard Template Library, which is part of the standard C++ library (see Item E49 and Item 35). Objects of type list behave like doubly linked lists, though they need not be implemented in that way.NewsLetter objects, when not being worked on, would likely be stored on disk. To support the creation of a Newsletter from its on-disk representation, it would be convenient to give NewsLetter a constructor that takes an istream. The constructor would read information from the stream as it created the necessary in-core data structures: class NewsLetter {public:  NewsLetter(istream& str);  ...Pseudocode for this constructor might look like this, NewsLetter::NewsLetter(istream& str){  while (str) {    read the next component object from str;    add the object to the list of this    newsletter's components;  }}or, after moving the tricky stuff into a separate function called readComponent, like this: class NewsLetter {public:  ...private:  // read the data for the next NLComponent from str,  // create the component and return a pointer to it  static NLComponent * readComponent(istream& str);  ...};NewsLetter::NewsLetter(istream& str){  while (str) {    // add the pointer returned by readComponent to the    // end of the components list; "push_back" is a list    // member function that inserts at the end of the list    components.push_back(readComponent(str));  }}Consider what readComponent does. It creates a new object, either a TextBlock or a Graphic, depending on the data it reads. Because it creates new objects, it acts much like a constructor, but because it can create different types of objects, we call it a virtual constructor. A virtual constructor is a function that creates different types of objects depending on the input it is given. Virtual constructors are useful in many contexts, only one of which is reading object information from disk (or off a network connection or from a tape, etc.).A particular kind of virtual constructor the virtual copy constructor is also widely useful. A virtual copy constructor returns a pointer to a new copy of the object invoking the function. Because of this behavior, virtual copy constructors are typically given names like copySelf, cloneSelf, or, as shown below, just plain clone. Few functions are implemented in a more straightforward manner: class NLComponent {public:  // declaration of virtual copy constructor  virtual NLComponent * clone() const = 0;  ...};class TextBlock: public NLComponent {public:  virtual TextBlock * clone() const         // virtual copy  { return new TextBlock(*this); }          // constructor  ...};class Graphic: public NLComponent {public:  virtual Graphic * clone() const            // virtual copy  { return new Graphic(*this); }             // constructor  ...};As you can see, a class's virtual copy constructor just calls its real copy constructor. The meaning of "copy" is hence the same for both functions. If the real copy constructor performs a shallow copy, so does the virtual copy constructor. If the real copy constructor performs a deep copy, so does the virtual copy constructor. If the real copy constructor does something fancy like reference counting or copy-on-write (see Item 29), so does the virtual copy constructor. Consistency what a wonderful thing.Notice that the above implementation takes advantage of a relaxation in the rules for virtual function return types that was adopted relatively recently. No longer must a derived class's redefinition of a base class's virtual function declare the same return type. Instead, if the function's return type is a pointer (or a reference) to a base class, the derived class's function may return a pointer (or reference) to a class derived from that base class. This opens no holes in C++'s type system, and it makes it possible to accurately declare functions such as virtual copy constructors. That's why TextBlock's clone can return a TextBlock* and Graphic's clone can return a Graphic*, even though the return type of NLComponent's clone is NLComponent*.The existence of a virtual copy constructor in NLComponent makes it easy to implement a (normal) copy constructor for NewsLetter: class NewsLetter {public:  NewsLetter(const NewsLetter& rhs);  ...private:  list<NLComponent*> components;};NewsLetter::NewsLetter(const NewsLetter& rhs){  // iterate over rhs's list, using each element's  // virtual copy constructor to copy the element into  // the components list for this object. For details on  // how the following code works, see Item 35.  for (list<NLComponent*>::const_iterator it =          rhs.components.begin();       it != rhs.components.end();       ++it) {    // "it" points to the current element of rhs.components,    // so call that element's clone function to get a copy    // of the element, and add that copy to the end of    // this object's list of components    components.push_back((*it)->clone());  }}Unless you are familiar with the Standard Template Library, this code looks bizarre, I know, but the idea is simple: just iterate over the list of components for the NewsLetter object being copied, and for each component in the list, call its virtual copy constructor. We need a virtual copy constructor here, because the list contains pointers to NLComponent objects, but we know each pointer really points to a TextBlock or a Graphic. We want to copy whatever the pointer really points to, and the virtual copy constructor does that for us.Making Non-Member Functions Act VirtualJust as constructors can't really be virtual, neither can non-member functions (see Item E19). However, just as it makes sense to conceive of functions that construct new objects of different types, it makes sense to conceive of non-member functions whose behavior depends on the dynamic types of their parameters. For example, suppose you'd like to implement output operators for the TextBlock and Graphic classes. The obvious approach to this problem is to make the output operator virtual. However, the output operator is operator<<, and that function takes an ostream& as its left-hand argument; that effectively rules out the possibility of making it a member function of the TextBlock or Graphic classes.(It can be done, but then look what happens: class NLComponent {public:  // unconventional declaration of output operator  virtual ostream& operator<<(ostream& str) const = 0;  ...};class TextBlock: public NLComponent {public:  // virtual output operator (also unconventional)  virtual ostream& operator<<(ostream& str) const;};class Graphic: public NLComponent {public:  // virtual output operator (still unconventional)  virtual ostream& operator<<(ostream& str) const;};TextBlock t;Graphic g;...t << cout;                                  // print t on cout via                                            // virtual operator<<; note                                            // unconventional syntaxg << cout;                                  // print g on cout via                                            // virtual operator<<; note                                            // unconventional syntaxClients must place the stream object on the right-hand side of the "<<" symbol, and that's contrary to the convention for output operators. To get back to the normal syntax, we must move operator<< out of the TextBlock and Graphic classes, but if we do that, we can no longer declare it virtual.)An alternate approach is to declare a virtual function for printing (e.g., print) and define it for the TextBlock and Graphic classes. But if we do that, the syntax for printing TextBlock and Graphic objects is inconsistent with that for the other types in the language, all of which rely on operator<< as their output operator.Neither of these solutions is very satisfying. What we want is a non-member function called operator<< that exhibits the behavior of a virtual function like print. This description of what we want is in fact very close to a description of how to get it. We define both operator<< and print and have the former call the latter! class NLComponent {public:  virtual ostream& print(ostream& s) const = 0;  ...};class TextBlock: public NLComponent {public:  virtual ostream& print(ostream& s) const;  ...};class Graphic: public NLComponent {public:  virtual ostream& print(ostream& s) const;  ...};inlineostream& operator<<(ostream& s, const NLComponent& c){  return c.print(s);}Virtual-acting non-member functions, then, are easy. You write virtual functions to do the work, then write a non-virtual function that does nothing but call the virtual function. To avoid incurring the cost of a function call for this syntactic sleight-of-hand, of course, you inline the non-virtual function (see Item E33).

⌨️ 快捷键说明

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