📄 ec7.htm
字号:
Effective C++, 2E | Chapter 7: Miscellany Back to Item 44: Say what you mean; understand what you're saying.Continue to Item 45: Know what functions C++ silently writes and calls.MiscellanySome guidelines for effective C++ programming defy convenient categorization. This section is where such guidelines come to roost. Not that that diminishes their importance. If you are to write effective software, you must understand what compilers are doing for you (to you?) behind your back, how to ensure that non-local static objects are initialized before they are used, what you can expect from the standard library, and where to go for insights into the language's underlying design philosophy. In this final section of the book, I expound on these issues, and more. Back to MiscellanyContinue to Item 46: Prefer compile-time and link-time errors to runtime errors.Item 45: Know what functions C++ silently writes and calls.When is an empty class not an empty class? When C++ gets through with it. If you don't declare them yourself, your thoughtful compilers will declare their own versions of a copy constructor, an assignment operator, a destructor, and a pair of address-of operators. Furthermore, if you don't declare any constructors, they will declare a default constructor for you, too. All these functions will be public. In other words, if you write this, class Empty{};it's the same as if you'd written this: class Empty {public: Empty(); // default constructor Empty(const Empty& rhs); // copy constructor ~Empty(); // destructor see // below for whether // it's virtual Empty& operator=(const Empty& rhs); // assignment operator Empty* operator&(); // address-of operators const Empty* operator&() const;};Now these functions are generated only if they are needed, but it doesn't take much to need them. The following code will cause each function to be generated: const Empty e1; // default constructor; // destructorEmpty e2(e1); // copy constructore2 = e1; // assignment operatorEmpty *pe2 = &e2; // address-of // operator (non-const)const Empty *pe1 = &e1; // address-of // operator (const)Given that compilers are writing functions for you, what do the functions do? Well, the default constructor and the destructor don't really do anything. They just enable you to create and destroy objects of the class. (They also provide a convenient place for implementers to place code whose execution takes care of "behind the scenes" behavior see Items 33 and M24.) Note that the generated destructor is nonvirtual (see Item 14) unless it's for a class inheriting from a base class that itself declares a virtual destructor. The default address-of operators just return the address of the object. These functions are effectively defined like this: inline Empty::Empty() {}inline Empty::~Empty() {}inline Empty * Empty::operator&() { return this; }inline const Empty * Empty::operator&() const{ return this; }As for the copy constructor and the assignment operator, the official rule is this: the default copy constructor (assignment operator) performs memberwise copy construction (assignment) of the nonstatic data members of the class. That is, if m is a nonstatic data member of type T in a class C and C declares no copy constructor (assignment operator), m will be copy constructed (assigned) using the copy constructor (assignment operator) defined for T, if there is one. If there isn't, this rule will be recursively applied to m's data members until a copy constructor (assignment operator) or built-in type (e.g., int, double, pointer, etc.) is found. By default, objects of built-in types are copy constructed (assigned) using bitwise copy from the source object to the destination object. For classes that inherit from other classes, this rule is applied to each level of the inheritance hierarchy, so user-defined copy constructors and assignment operators are called at whatever level they are declared.I hope that's crystal clear.But just in case it's not, here's an example. Consider the definition of a NamedObject template, whose instances are classes allowing you to associate names with objects: template<class T>class NamedObject {public: NamedObject(const char *name, const T& value); NamedObject(const string& name, const T& value); ...private: string nameValue; T objectValue;};Because the NamedObject classes declare at least one constructor, compilers won't generate default constructors, but because the classes fail to declare copy constructors or assignment operators, compilers will generate those functions (if they are needed).Consider the following call to a copy constructor: NamedObject<int> no1("Smallest Prime Number", 2);NamedObject<int> no2(no1); // calls copy constructorThe copy constructor generated by your compilers must initialize no2.nameValue and no2.objectValue using no1.nameValue and no1.objectValue, respectively. The type of nameValue is string, and string has a copy constructor (which you can verify by examining string in the standard library see Item 49), so no2.nameValue will be initialized by calling the string copy constructor with no1.nameValue as its argument. On the other hand, the type of NamedObject<int>::objectValue is int (because T is int for this template instantiation), and no copy constructor is defined for ints, so no2.objectValue will be initialized by copying the bits over from no1.objectValue.The compiler-generated assignment operator for NamedObject<int> would behave the same way, but in general, compiler-generated assignment operators behave as I've described only when the resulting code is both legal and has a reasonable chance of making sense. If either of these tests fails, compilers will refuse to generate an operator= for your class, and you'll receive some lovely diagnostic during compilation.For example, suppose NamedObject were defined like this, where nameValue is a reference to a string and objectValue is a const T: template<class T>class NamedObject {public: // this ctor no longer takes a const name, because name- // Value is now a reference-to-non-const string. The char* // ctor is gone, because we must have a string to refer to NamedObject(string& name, const T& value); ... // as above, assume no // operator= is declaredprivate: string& nameValue; // this is now a reference const T objectValue; // this is now const};Now consider what should happen here: string newDog("Persephone");string oldDog("Satch");NamedObject<int> p(newDog, 2); // as I write this, our dog
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -