EC Notes - yszheda/wiki GitHub Wiki
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.
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的困惑。
Meyers很喜欢举的例子,smart pointer的创建需要保证原子性,但创建语句写在传实参时,compiler的out-of-order优化会破坏这种原子性,从而破坏smart pointer所要确保的RAII。
- 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.
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.
- 不返回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.)
- 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.
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.
-- TODO
for (int i = 0; i < n; i++)
{
Widget w(...);
...
}
-
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
...
}
};
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());
When an exception is thrown, exception-safe functions:
- Leak no resources.
- Don’t allow data structures to become corrupted.
virtual functions are dynamically bound, but default parameter values are statically bound.