1、For the most part, coming up with appropriate definitions for your classes (and class templates) and appropriate declarations for your functions (and function templates) is the lion's share of the battle. Once you've got those right, the corresponding implementations are largely straightforward. Still, there are things to watch out for. Defining variables too soon can cause a drag on performance. Overuse of casts can lead to code that's slow, hard to maintain, and infected with subtle bugs. Returning handles to an object's internals can defeat encapsulation and leave clients with dangling handles. Failure to consider the impact of exceptions can lead to leaked resources and corrupted data structures. Overzealous inlining can cause code bloat. Excessive coupling can result in unacceptably long build times.
<Item 26> Postpone variable definitions as long as possible
2、推迟变量的声明在C++中比在C中重要的多。在C语言中声明一个变量开销仅仅是堆栈上面的空间(通常只是在递归函数中才仔细考虑其性能影响),而在C++中则意味着构造函数和析构函数潜在的运行开销
3、Not only should you postpone a variable's definition until right before you have to use the variable, you should also try to postpone the definition until you have initialization arguments for it. By doing so, you avoid constructing and destructing unneeded objects, and you avoid unnecessary default constructions. Further, you help document the purpose of variables by initializing them in contexts in which their meaning is clear. 包含两方面 一个是推迟变量的定义 一个是在定义变量的时候直接初始化,避免调用了默认构造函数在赋值
// finally, the best way to define and initialize encryptedstd::string encryptPassword(const std::string& password){ if (password.length() < MinimumPasswordLength) { throw logic_error("Password is too short"); // check length } std::string encrypted(password); // define and initialize // via copy constructor encrypt(encrypted); return encrypted;}
4、对于循环中的变量,有下面两种方案
// Approach A: define outside loop // Approach B: define inside loopWidget w;for (int i = 0; i < n; ++i){ for (int i = 0; i < n; ++i) { w = some value dependent on i; Widget w(some value dependent on i); ... ...} }
- Approach A: 1 constructor + 1 destructor + n assignments.
-
Approach B: n constructors + n destructors.
但是A对代码的可理解性和可维护性产生了负面影响,As a result, unless you know that (1) assignment is less expensive than a constructor-destructor pair 即A更高效 and (2) you're dealing with a performance-sensitive part of your code,性能敏感, 两个条件满足时使用A。you should default to using Approach B.
5、Things to Remember
-
Postpone variable definitions as long as possible. It increases program clarity and improves program efficiency.
<Item 27> Minimize casting
6、The rules of C++ are designed to guarantee that type errors are impossible. In theory, if your program compiles cleanly, it's not trying to perform any unsafe or nonsensical operations on any objects. This is a valuable guarantee. You don't want to forgo it lightly.
7、Unfortunately, casts subvert the type system. That can lead to all kinds of trouble, some easy to recognize, some extraordinarily subtle. If you're coming to C++ from C, Java, or C#, take note, because casting in those languages is more necessary and less dangerous than in C++. But C++ is not C. It's not Java. It's not C#. In this language, casting is a feature you want to approach with great respect.
8、Let's begin with a review of casting syntax, because there are usually three different ways to write the same cast. C-style casts look like this:
(T) expression // cast expression to be of type T
Function-style casts use this syntax:
T(expression) // cast expression to be of type T
There is no difference in meaning between these forms; it's purely a matter of where you put the parentheses. I call these two forms old-style casts.
9、C++ also offers four new cast forms (often called new-style or C++-style casts):
const_cast(expression)dynamic_cast (expression) reinterpret_cast (expression) static_cast (expression)
Each serves a distinct purpose:
-
-
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. (I'll provide details on this a bit later.)
-
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. I use it only once in this book, and that's only when discussing how you might write a debugging allocator for raw memory (see ).
-
static_cast can be used to force implicit conversions (e.g., non-const object to const object (as in ), 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.)
-
10、需要显示进行类型转换的往往是代码设计的薄弱点,但是很难完全避免。const_cast使用时需要谨慎,const在c++中重要程度远高于c,c++提供了mutable用于解决看起来需要const_cast解决的问题
11、The old-style casts continue to be legal, but the new forms are preferable. First, they're much easier to identify in code (both for humans and for tools like grep), thus simplifying the process of finding places in the code where the type system is being subverted. Second, the more narrowly specified purpose of each cast makes it possible for compilers to diagnose usage errors. For example, if you try to cast away constness using a new-style cast other than const_cast, your code won't compile.
12、About the only time I use an old-style cast is when I want to call an explicit constructor to pass an object to a function. For example:
class Widget {public: explicit Widget(int size); ...};void doSomeWork(const Widget& w);doSomeWork(Widget(15)); // create Widget from int // with function-style castdoSomeWork(static_cast(15)); // create Widget from int // with C++-style cast
Somehow, deliberate object creation doesn't "feel" like a cast, so I'd probably use the function-style cast instead of the static_cast in this case. Then again, code that leads to a core dump usually feels pretty reasonable when you write it, so perhaps you'd best ignore feelings and use new-style casts all the time.
13、Many programmers believe that casts do nothing but tell compilers to treat one type as another, but this is mistaken. Type conversions of any kind (either explicit via casts or implicit by compilers) often lead to code that is executed at runtime. For example, in this code fragment,
int x, y;...double d = static_cast(x)/y; // divide x by y, but use // floating point division
the cast of the int x to a double almost certainly generates code, because on most architectures, the underlying representation for an int is different from that for a double. That's perhaps not so surprising, but this example may widen your eyes a bit:
class Base { ... };class Derived: public Base { ... };Derived d;Base *pb = &d; // implicitly convert Derived* 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. For example, casting object addresses to char* pointers and then using pointer arithmetic on them almost always yields undefined behavior.
14、But note that I said that an offset is "sometimes" required. The way objects are laid out and the way their addresses are calculated varies from compiler to compiler. That means that just because your "I know how things are laid out" casts work on one platform doesn't mean they'll work on others. The world is filled with woeful programmers who've learned this lesson the hard way.
15、典型的类型转换导致的错误如下,红色部分实际在*this对象基类的拷贝上调用的onResize,紫色部分才是在*this对象基类上调用的。This example also demonstrates that if you find yourself wanting to cast, it's a sign that you could be approaching things the wrong way. This is especially the case if your want is for dynamic_cast.
class Window { // base classpublic: virtual void onResize() { ... } // base onResize impl ...};class SpecialWindow: public Window { // derived classpublic: virtual void onResize() { // derived onResize impl; static_cast(*this).onResize(); // cast *this to Window, // then call its onResize; // this doesn't work! Window::onResize(); // call Window::onResize// on *this ... // do SpecialWindow- } // specific stuff ...};
16、Before delving into the design implications of dynamic_cast, it's worth observing that many implementations of dynamic_cast can be quite slow. For example, at least one common implementation is based in part on string comparisons of class names. If you're performing a dynamic_cast on an object in a single-inheritance hierarchy four levels deep, each dynamic_cast under such an implementation could cost you up to four calls to strcmp to compare class names. A deeper hierarchy or one using multiple inheritance would be more expensive. There are reasons that some implementations work this way (they have to do with support for dynamic linking). Nonetheless, in addition to being leery of casts in general, you should be especially leery of dynamic_casts in performance-sensitive code.The need for dynamic_cast generally arises because you want to perform derived class operations on what you believe to be a derived class object, but you have only a pointer- or reference-to-base through which to manipulate the object. There are two general ways to avoid this problem.Neither of these approaches — using type-safe containers (直接保存派生类的指针避免cast) or moving virtual functions up the hierarchy(在基类中声明派生类函数的虚函数,直接使用基类指针调用避免cast) — is universally applicable, but in many cases, they provide a viable alternative to dynamic_casting. When they do, you should embrace them.
17、Like most suspicious constructs, casts should be isolated as much as possible, typically hidden inside functions whose interfaces shield callers from the grubby work being done inside.
18、Things to Remember
-
Avoid casts whenever practical, especially dynamic_casts in performance-sensitive code. If a design requires casting, try to develop a cast-free alternative.
-
When casting is necessary, try to hide it inside a function. Clients can then call the function instead of putting casts in their own code.
-
Prefer C++-style casts to old-style casts. They are easier to see, and they are more specific about what they do.
<Item 28> Avoid returning "handles" to object internals.
19、This immediately leads to two lessons. First, a data member is only as encapsulated as the most accessible function returning a reference to it(数据成员的封装性等于最宽松的访问函数的封装性). In this case, though ulhc and lrhc are declared private, they're effectively public, because the public functions upperLeft and lowerRight return references to them. Second, if a const member function returns a reference to data associated with an object that is stored outside the object itself, the caller of the function can modify that data, (This is just a fallout of the limitations of bitwise constness — see .)
20、Everything we've done has involved member functions returning references, but if they returned pointers or iterators, the same problems would exist for the same reasons. 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. As we've seen, it can also lead to const member functions that allow an object's state to be modified.
21、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.
22、dangling handles(悬空引用): handles that refer to parts of objects that don't exist any longer ,如下面代码&(boundingBox(*pgo).upperLeft());语句执行完后(boundingBox(*pgo)产生的临时对象就会被销毁,因此pUpperLeft 指向了一个销毁的内部数据。
class GUIObject { ... };const Rectangle // returns a rectangle by boundingBox(const GUIObject& obj); // value; see Item 3 for why // return type is constGUIObject *pgo; // make pgo point to... // some GUIObjectconst Point *pUpperLeft = // get a ptr to the upper &(boundingBox(*pgo).upperLeft()); // left point of its // bounding box
23、This doesn't mean that you should never have a member function that returns a handle. Sometimes you have to. For example, operator[] allows you to pluck individual elements out of strings and vectors, and these operator[]s work by returning references to the data in the containers (see ) — data that is destroyed when the containers themselves are. Still, such functions are the exception, not the rule.
24、Things to Remember
-
Avoid returning handles (references, pointers, or iterators) to object internals. It increases encapsulation, helps const member functions act const, and minimizes the creation of dangling handles.