EC Notes - yszheda/wiki GitHub Wiki

Item 4: Make sure that objects are initialized before they're used

To avoid reader confusion, as well as the possibility of some truly obscure behavioral bugs, always list members in the initialization list in the same order as they're declared in the class.

Item 15: Provide access to raw resources in resource-managing classes.

Item 16: Use the same form in corresponding uses of new and delete.

When you employ a new expression (i.e., dynamic creation of an object via a use of new ), two things happen. First, memory is allocated (via a function named operator new —see Items 49(See 16.1) and 51(See 16.3)). Second, one or more constructors are called for that memory. When you employ a delete expression (i.e., use delete ), two other things happen: one or more destructors are called for the memory, then the memory is deallocated (via a function named operator delete—see Item 51(See 16.3)). The big question for delete is this: how many objects reside in the memory being deleted? The answer to that determines how many destructors must be called.

对数组应用typedef会造成使用何种delete的困惑。

Item 17: Store newed objects in smart pointers in standalone statements.

Meyers很喜欢举的例子,smart pointer的创建需要保证原子性,但创建语句写在传实参时,compiler的out-of-order优化会破坏这种原子性,从而破坏smart pointer所要确保的RAII。

Designs and Declarations

Item 18: Make interfaces easy to use correctly and hard to use incorrectly

Things to Remember

  • Good interfaces are easy to use correctly and hard to use incorrectly. You should strive for these characteristics in all your interfaces.
  • Ways to facilitate correct use include consistency in interfaces and behavioral compatibility with built-in types.
  • Ways to prevent errors include creating new types, restricting operations on types, constraining object values, and eliminating client resource management responsibilities.
  • tr1::shared_ptr supports custom deleters. This prevents the cross-DLL problem, can be used to automatically unlock mutexes (see Item 14), etc.

“cross-DLL problem”: its default deleter uses delete from the same DLL where the tr1::shared_ptr is created.

Item 19: Treat class design as type design

Item 20: Prefer pass-by-reference-to-const to pass-by-value

Passing parameters by reference also avoids the slicing problem. When a derived class object is passed (by value) as a base class object, the base class copy constructor is called, and the specialized features that make the obje ct behave like a derived class object are "sliced" off.

references are typically implemented as pointers, so passing something by reference usually means really passing a pointer. As a result, if you have an object of a built- in type (e.g., an int ), it's often more efficient to pass it by value than by reference. For built- in types, then, when you have a choice between pass-by- value and pass-by-reference-to- const , it's not unreasonable to choose pass-by- value. This same advice applies to iterators and function objects in the STL, because, by convention, they are designed to be passed by value.

Item 21: Don't try to return a reference when you must return an object

  • 不返回reference的原因:
    • a reference is just a name, a name for some existing object. (无法确保reference对应的object在函数调用前就已创建好->在函数内生成->两种方式,create on stack/heap)
    • 返回函数内stack对象的reference是典型memory leak
    • 返回函数内heap对象的reference无法确保被正确释放
    • 返回函数内static对象的reference会使需要不同结果副本的逻辑出错,还有线程安全问题

Never return a pointer or reference to a local stack object, a refere nce to a heap-allocated object, or a pointer or reference to a local static object if there is a chance that more than one such object will be needed. (Item 4(See 9.4) provides an example of a design where returning a reference to a local static is reasonable, at least in single-threaded environments.)

Item 22: Declare data members private

  • Declare data members private . It gives clients syntactically uniform access to data, affords fine-grained access control, allows invariants to be enforced, and offers class authors implementation flexibility.
  • protected is no more encapsulated than public.

Item 23: Prefer non-member non-friend functions to member functions

If something is encapsulated, it's hidden from view. The more something is encapsulated, the fewer things can see it. The fewer things can see it, the greater flexibility we have to change it, because our changes directly affect only those things that can see what we change. The greater something is encapsulated, then, the greater our ability to change it. That's the reason we value encapsulation in the first place: it affords us the flexibility to change things in a way that affects only a limited number of clients.

Consider the data associated with an object. The less code that ca n see the data (i.e., access it), the more the data is encapsulated, and the more freely we can change characteristics of an object's data, such as the number of data members, their types, etc. As a coarse-grained measure of how much code can see a piece o f data, we can count the number of functions that can access that data: the more functions that can access it, the less encapsulated the data.

Item 22(See 12.5) explains that data members should be private, because if they're not, an unlimited number of functions can access them. They have no encapsulation at all. For data members that are private, the number of functions that can access them is the number of member functions of the class plus the number of friend functions, because only members and friends have access to private members. Given a choice between a member function (which can access not only the private data of a class, but also private functions, enums, typedefs, etc.) and a non- member non-friend function (which can access none of these things) providing the same functionality, the choice yielding greater encapsulation is the non-member non- friend function, because it doesn't increase the number of functions that can access the private parts of the class.

  • Prefer non-member non-friend functions to member functions. Doing so increases encapsulation, packaging flexibility, and functional extensibility.

Item 24: Declare non-member functions when type conversions should apply to all parameters

Item 25: Consider support for a non-throwing swap

-- TODO

Implementations

Item 26: Postpone variable definitions as long as possible.

for (int i = 0; i < n; i++)
{
  Widget w(...);
  ...
}

Item 27: Minimize casting.

  • const_cast is typically used to cast away the constness of objects. It is the only C++-style cast that can do this.
  • dynamic_cast is primarily used to perform "safe downcasting," i.e., to determine whether an object is of a particular type in an inheritance hierarchy. It is the only cast that cannot be performed using the old-style syntax. It is also the only cast that may have a significant runtime cost.
  • reinterpret_cast is intended for low-level casts that yield implementation-dependent (i.e., unportable) results, e.g., casting a pointer to an int . Such casts should be rare outside low-level code.
  • static_cast can be used to force implicit conversions (e.g., non-const object to const object (as in Item 3(See 9.3)), int to double , etc.). It can also be used to perform the reverse of many such conversions (e.g., void* pointers to typed pointers, pointer-to-base to pointer-to-derived), though it cannot cast from const to non-const objects. (Only const_cast can do that.)

重要:

class Base { ... };
class Derived: public Base { ... };
Derived d;
Base *pb = &d;  // implicitly convert Derived* to Base*

Here we're just creating a base class pointer to a derived class object, but sometimes, the two pointer values will not be the same. When that's the case, an offset is applied at runtime to the Derived* pointer to get the correct Base* pointer value.

This last example demonstrates that a single object (e.g., an object of type Derived ) might have more than one address (e.g., its address when pointed to by a Base* pointer and its address when pointed to by a Derived* pointer). That can't happen in C. It can't happen in Java. It can't happen in C#. It does happen in C++. In fact, when multiple inheritance is in use, it happens virtually all the time, but it can happen under single inheritance, too. Among other things, that means you should generally avoid making assumptions about how things are laid out in C++, and you should certainly not perform casts based on such assumptions.

class Window {
// base class
public:
virtual void onResize() { ... }  // base onResize impl
...
};


class SpecialWindow: public Window {
// derived class
public:
virtual void onResize() { // derived onResize impl;
    static_cast<Window>(*this).onResize();  // cast *this to Window, then call its onResize
    // this doesn't work!
    // do SpecialWindow specific stuff
    ...
}
};

Instead, the cast creates a new, temporary copy of the base class part of *this, then invokes onResize on the copy! The above code doesn't call Window::onResize on the current object and then perform the SpecialWindow-specific actions on that object — it calls Window::onResize on a copy of the base class part of the current object before performing SpecialWindow-specific actions on the current object. If Window::onResize modifies the current object (hardly a remote possibility, since onResize is a non- const member function), the current object won't be modified. Instead, a copy of that object will be modified. If SpecialWindow::onResize modifies the current object, however, the current object will be modified, leading to the prospect that the code will leave the current object in an invalid state, one where base class modifications have not been made, but derived class ones have been.

class SpecialWindow: public Window {
// derived class
public:
virtual void onResize() { // derived onResize impl;
    Window::onResize();  // call Window::onResize on *this
    ...
}
};

Item 28: Avoid returning "handles" to object internals.

References, pointers, and iterators are all handles (ways to get at other objects), and returning a handle to an object’s internals always runs the risk of compromising an object’s encapsulation.

We generally think of an object’s “internals” as its data members, but member functions not accessible to the general public (i.e., that are protected or private) are part of an object’s internals, too. As such, it’s important not to return handles to them. This means you should never have a member function return a pointer to a less accessible member function. If you do, the effective access level will be that of the more accessible function, because clients will be able to get a pointer to the less accessible function, then call that function through the pointer.

E.g. 私有成员pData可由upperLeft/lowerRight传引用后在外部改变,破坏封装和const成员函数。

class Point { // class for representing points
    public:
        Point(int x, int y);
        void setX(int newVal);
        void setY(int newVal);
};

struct RectData {   // Point data for a Rectangle
    Point ulhc;     // ulhc = "upper left-hand corner"
    Point lrhc;     // lrhc = "lower right-hand corner"
};

class Rectangle {
    public:
        // ...
        Point& upperLeft() const { return pData->ulhc; }
        Point& lowerRight() const { return pData->lrhc; }
    private:
        std::tr1::shared_ptr<RectData> pData; // see Item13 for info on tr1::shared_ptr
};

采用const引用作为返回可保证const成员函数特性,但可能会引发dangling handles问题。

class Rectangle {
    public:
        // omited
        const Point& upperLeft() const { return pData->ulhc; }
        const Point& lowerRight() const { return pData->lrhc; }
        // omited
};

class GUIObject { ... };

// returns a rectangle by value; see Item3 for why return type is const
const Rectangle boundingBox(const GUIObject& obj);

GUIObject *pgo;
// make pgo point to some GUIObject
// omited
//
// get a ptr to the upper left point of its bounding box
// dangling handle!
// boundingBox(*pgo)是一个临时对象
// 它被销毁后之前调用upperLeft()所返回的成员引用也被销毁
// pUpperLeft是dangling的!
const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());

Item 29: Strive for exception-safe code.

When an exception is thrown, exception-safe functions:

  • Leak no resources.
  • Don’t allow data structures to become corrupted.

Item 31: Minimize compilation dependencies between files.

Inheritance and Object-Oriented Design

Item 32: Make sure public inheritance models "is-a."

Item 33: Avoid hiding inherited names

Item 34: Differentiate between inheritance of interface and inheritance of implementation

Item 35: Consider alternatives to virtual functions

The Template Method Pattern via the Non-Virtual Interface Idiom

Strategy Pattern

Item 36: Never redefine an inherited non-virtual function

Item 37: Never redefine a function's inherited default parameter value

virtual functions are dynamically bound, but default parameter values are statically bound.

Item 38: Model "has-a" or "is-implemented-in-terms-of " through composition

Item 39: Use private inheritance judiciously

Item 40: Use multiple inheritance judiciously

Templates and Generic Programming

Item 41: Understand implicit interfaces and compile-time polymorphism

Item 42: Understand the two meanings of typename

Item 43: Know how to access names in templatized base classes

⚠️ **GitHub.com Fallback** ⚠️