1. Postpone variable definitions as long as possible. It increases program clarity and improves program efficiency. 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.
// Approach A: define outside loop // Approach B: define inside loop Widget 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); ... ... } }
Here I've switched from an object of type string to an object of typeWidget to avoid any preconceptions about the cost of performing a construction, destruction, or assignment for the object.
In terms of Widget operations, the costs of these two approaches are as follows:
-
Approach A: 1 constructor + 1 destructor +n assignments.
-
Approach B: n constructors +n destructors.
For classes where an assignment costs less than a constructor-destructor pair, Approach A is generally more efficient. This is especially the case asn gets large. Otherwise, Approach B is probably better. Furthermore, Approach A makes the namew visible in a larger scope (the one containing the loop) than Approach B, something that's contrary to program comprehensibility and maintainability. As a result, unless you know that (1) assignment is less expensive than a constructor-destructor pair and (2) you're dealing with a performance-sensitive part of your code, you should default to using Approach B.
2.
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
-
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. (many implementations ofdynamic_cast can be quite slow)
It is used when you only have a base class pointer to derived class object but you want to access some function in derived object(non- virtual function).
The dynamic_cast operator ensures that if you convert a pointer of class A to a pointer of a class B, the object that A points to belongs to class B or a class derived from B.(otherwise the cast will fail)
#include <iostream> using namespace std; struct A { virtual void f() { cout << "Class A" << endl; } }; struct B : A { virtual void f() { cout << "Class B" << endl; } }; struct C : A { virtual void f() { cout << "Class C" << endl; } }; void f(A* arg) { B* bp = dynamic_cast<B*>(arg); C* cp = dynamic_cast<C*>(arg); if (bp) bp->f(); else if (cp) cp->f(); else arg->f(); }; int main() { A aobj; C cobj; A* ap = &cobj; A* ap2 = &aobj; f(ap); f(ap2); }
The following is the output of the above example:
Class C Class A
-
reinterpret_cast is intended for low-level casts that yield implementation-dependent (i.e., unportable) results, e.g., casting a pointer to anint. 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 (seeItem 50).
-
static_cast can be used to force implicit conversions (e.g., non-const object toconst object (as inItem 3),int todouble, 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 fromconst to non-const objects. (Onlyconst_cast can do that.)
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.
class Widget { public: explicit Widget(int size); ... }; void doSomeWork(const Widget& w); doSomeWork(Widget(15)); // create Widget from int // with function-style cast it is not calling constructor doSomeWork(static_cast<Widget>(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 thestatic_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.
3.This last example demonstrates that a single object (e.g., an object of typeDerived) might have more than one address (e.g., its address when pointed to by aBase* pointer and its address when pointed to by aDerived* pointer).
4. 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.
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 ... };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.
5.
When an exception is thrown, exception-safe functions:
-
Leak no resources. The code above fails this test, because if the "new Image(imgSrc)" expression yields an exception, the call tounlock never gets executed, and the mutex is held forever.
-
Don't allow data structures to become corrupted. If "new Image(imgSrc)" throws,bgImage is left pointing to a deleted object. In addition,imageChanges has been incremented, even though it's not true that a new image has been installed. (On the other hand, the old image has definitely been eliminated, so I suppose you could argue that the image has been "changed.")
Exception-safe functions offer one of three guarantees:
-
Functions offering the basic guarantee promise that if an exception is thrown, everything in the program remains in a valid state. No objects or data structures become corrupted, and all objects are in an internally consistent state (e.g., all class invariants are satisfied). However, the exact state of the program may not be predictable. For example, we could writechangeBackground so that if an exception were thrown, thePrettyMenu object might continue to have the old background image, or it might have some default background image, but clients wouldn't be able to predict which. (To find out, they'd presumably have to call some member function that would tell them what the current background image was.)
-
Functions offering the strong guarantee promise that if an exception is thrown, the state of the program is unchanged. Calls to such functions areatomic in the sense that if they succeed, they succeed completely, and if they fail, the program state is as if they'd never been called.
Working with functions offering the strong guarantee is easier than working with functions offering only the basic guarantee, because after calling a function offering the strong guarantee, there are only two possible program states: as expected following successful execution of the function, or the state that existed at the time the function was called. In contrast, if a call to a function offering only the basic guarantee yields an exception, the program could be inany valid state.
-
Functions offering the nothrow guarantee promise never to throw exceptions, because they always do what they promise to do. All operations on built-in types (e.g.,ints, pointers, etc.) are nothrow (i.e., offer the nothrow guarantee). This is a critical building block of exception-safe code.
-
int doSomething() throw(); // note empty exception spec.
This doesn't say that doSomething will never throw an exception; it says thatifdoSomething tHRows an exception, it's a serious error, and theunexpected function should be called.[1] In fact,doSomething may not offer any exception guarantee at all. The declaration of a function (including its exception specification, if it has one) doesn't tell you whether a function is correct or portable or efficient, and it doesn't tell you which, if any, exception safety guarantee it offers, either. All those characteristics are determined by the function's implementation, not its declaration.
struct PMImpl { // PMImpl = "PrettyMenu std::tr1::shared_ptr<Image> bgImage; // Impl."; see below for int imageChanges; // why it's a struct }; class PrettyMenu { ... private: Mutex mutex; std::tr1::shared_ptr<PMImpl> pImpl; }; void PrettyMenu::changeBackground(std::istream& imgSrc) { using std::swap; // see Item 25 Lock ml(&mutex); // acquire the mutex std::tr1::shared_ptr<PMImpl> // copy obj. data pNew(new PMImpl(*pImpl)); pNew->bgImage.reset(new Image(imgSrc)); // modify the copy ++pNew->imageChanges; swap(pImpl, pNew); // swap the new // data into place } // release the mutex
6.
If an inline function body is very short, the code generated for the function body may be smaller than the code generated for a function call. If that is the case, inlining the function may actually lead to smaller object code and a higher instruction cache hit rate!
Bear in mind that inline is a request to compilers, not a command. The request can be given implicitly or explicitly. The implicit way is to define a function inside a class definition:
Inline functions must typically be in header files, because most build environments do inlining during compilation. In order to replace a function call with the body of the called function, compilers must know what the function looks like
Templates are typically in header files, because compilers need to know what a template looks like in order to instantiate it when it's used.
Before we do that, let's finish the observation that inline is a request that compilers may ignore. Most compilers refuse to inline functions they deem too complicated (e.g., those that contain loops or are recursive), and all but the most trivial calls to virtual functions defy inlining. This latter observation shouldn't be a surprise.virtual means "wait until runtime to figure out which function to call," andinline means "before execution, replace the call site with the called function." If compilers don't know which function will be called, you can hardly blame them for refusing to inline the function's body.
7.
Sometimes compilers generate a function body for an inline function even when they are perfectly willing to inline the function. For example, if your program takes the address of an inline function, compilers must typically generate an outlined function body for it. How can they come up with a pointer to a function that doesn't exist? Coupled with the fact that compilers typically don't perform inlining across calls through function pointers, this means that calls to an inline function may or may not be inlined, depending on how the calls are made:
inline void f() {...} // assume compilers are willing to inline calls to f void (*pf)() = f; // pf points to f ... f(); // this call will be inlined, because it's a "normal" call pf(); // this call probably won't be, because it's through // a function pointer
Don't use inline key word to constructor and destructor.
Library designers must evaluate the impact of declaring functions inline, because it's impossible to provide binary upgrades to the client visible inline functions in a library. In other words, if f is an inline function in a library, clients of the library compile the body of f into their applications. If a library implementer later decides to change f, all clients who've used f must recompile. This is often undesirable. On the other hand, if f is a non-inline function, a modification to f requires only that clients relink. This is a substantially less onerous burden than recompiling and, if the library containing the function is dynamically linked, one that may be absorbed in a way that's completely transparent to clients.
8.
#include <string> // standard library components // shouldn't be forward-declared #include <memory> // for tr1::shared_ptr; see below class PersonImpl; // forward decl of Person impl. class class Date; // forward decls of classes used in class Address; // Person interface class Person { public: Person(const std::string& name, const Date& birthday, const Address& addr); std::string name() const; std::string birthDate() const; std::string address() const; ... private: // ptr to implementation; std::tr1::shared_ptr<PersonImpl> pImpl; // see Item 13 for info on }; // std::tr1::shared_ptr
Avoid using objects when object references and pointers will do(As in the example above). You may define references and pointers to a type with only a declaration for the type. Defining objects of a type necessitates the presence of the type's definition.
Because by doing so, when we create an instance of the class, we don't need to know the size of the object we point to. If we just use object instead of reference, we can't create the instance by just knowing the declare.
Depend on class declarations instead of class definitions whenever you can. Note that you never need a class definition to declare a function using that class, not even if the function passes or returns the class type by value:
class Date; // class declaration Date today(); // fine — no definition void clearAppointments(Date d); // of Date is needed
Provide separate header files for declarations and definitions. In order to facilitate adherence to the above guidelines, header files need to come in pairs: one for declarations, the other for definitions. These files must be kept consistent, of course. If a declaration is changed in one place, it must be changed in both. As a result, library clients should always #include a declaration file instead of forward-declaring something themselves, and library authors should provide both header files.(include "class Data" in a seperate header file)
The general idea behind minimizing compilation dependencies is to depend on declarations instead of definitions. Two approaches based on this idea are Handle classes and Interface classes.
1. Handle Class:
#include "Person.h" // we're implementing the Person class, // so we must #include its class definition #include "PersonImpl.h" // we must also #include PersonImpl's class // definition, otherwise we couldn't call // its member functions; note that // PersonImpl has exactly the same // member functions as Person — their // interfaces are identical Person::Person(const std::string& name, const Date& birthday, const Address& addr) : pImpl(new PersonImpl(name, birthday, addr)) {} std::string Person::name() const { return pImpl->name(); }
2. Interface Class:class Person { public: virtual ~Person(); virtual std::string name() const = 0; virtual std::string birthDate() const = 0; virtual std::string address() const = 0; ... };
-