Effective C++
Items
- Item 1: View C++ as a federation of languages.
- Item 2: Prefer consts, enums, and inlines to #defines.
- Item 3: Use const whenever possible.
- Item 4: Make sure that objects are initialized before they’re used.
- Item 5: Know what functions C++ silently writes and calls.
- Item 6: Explicitly disallow the use of compiler-generated functions you do not want.
- Item 7: Declare destructors virtual in polymorphic base classes.
- Item 8: Prevent exceptions from leaving destructors.
- Item 9: Never call virtual functions during construction or destruction.
- Item 10: Have assignment operators return a reference to *this.
- Item 11: Handle assignment to self in operator=.
- Item 12: Copy all parts of an object.
- Item 13: Use objects to manage resources.
- Item 14: Think carefully about copying behavior in resource-manageing classes.
- Item 15: Provide access to raw resources in resource-managing classes.
- Item 16: Use the same form in corresponding uses of new and delete.
- Item 17: Store newed objects in smart pointers in standalone statements.
- Item 18: Make interfaces easy to use correctly and hard to use incorrectly.
- Item 19: Treat class design as type design.
- Item 20: Prefer pass-by-reference-to-const to pass-by-value.
Item 1: View C++ as a federation of languages.
C++ is a multiparadigm programming language, one supporting a commbination of procedural, object-oriented, functional, generic, and metaprogramming features.
| sublanguages | keywords |
| -----------------------|----------------------------|
| C | superior |
| Object-Oriented C++ | object-oriented |
| Template C++ | the generic programming |
| The STL | a template library |
it’s a federation of four sublanguages, each with its own conventions.
Things to Remember
Rules for effective C++ programming vary, depending on the part of C++ you are using.
Item 2: Prefer consts, enums, and inlines to #defines.
prefer the compiler to the preprocessor
the preprocessor’s blind substitution of the macro name in multiply copies, while the use of the constant should never result in more than one copy.
Class-specific constants that are static and of integral type(e.g., integer, chars, bools) are an exception. You can declare them and use them without providing a definition.
| #include <iostream>
using namespace std;
struct GamePlayer {
static const int NumTurns = 5;
int n[ NumTurns ];
};
const int GamePlayer::NumTurns;
int main()
{
cout << GamePlayer::NumTurns << endl;
cout << &GamePlayer::NumTurns << endl;
return 0;
}
|
Things to Remember
For simple constants, prefer const objects or enums to #defines.
For function-like macros, prefer inline functions to #defines.
Item 3: Use const whenever possible.
const Ration operation( const Rational &lhs, const Rational &rhs ); make (ab) = c error
bitwise constness and logical constness
Things to Remember
Declaring something const helps compilers detect usage errors. const can be applied to objects at any scope, to function parameters and return types, and to member function as a while.
Comilers enforce bitwise constness, but you should program using logical constness.
When const and non-const member functions have essentially identical implementations, code duplication can be avoid by having the non-const version call the const version.
Item 4: Make sure that objects are initialized before they're used.
always initialize your objects before you use them. make sure that all constructors initialize everything in the object. data members of an object are initialized before the body of a constructor is entered.
The assignment-based version first called default constructors to initialize data members, then promptly assigned new values on top of the default-constructed ones. For objects of built-in type, there is no difference in cost between initialization and assignment. compilers will automatically call default constructors for data members of user-defined types when those data members have no initializers on the member initialization list.
data members that are const or are references must be initialized; the easiest choice is to always use the initialization list.
base classes are initialized before derived classes, and within a class, data members are initialized in the order in which they are declared.
A static object is one that exists from the time it’s constructed until the end of the program.
Things to Remember
Manually initialize objects of built-in type, because C++ only sometimes initializes them itself.
In a constructor, prefer use of the member initialization list to assignment inside the body of the constructor. List data members in the initialization list in the same order they're declared in the class.
Avoid initialization order problems across translation units by replacing non-local static objects with local static objects.
Item 1: View C++ as a federation of languages.
Things to Remember
Compilers may implicity generate a class's default constructor, copy constructor, copy assignment operator, and destructor.
Item 6: Explicitly disallow the use of compiler-generated functions you do not want.
there are two methods.
To disallow functionality automatically provided by compilers, declare the corresponding member functions private and give no implementations. Using a base class like Uncopyable is one way to do this.
| class Uncopyable {
protected:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable( const Uncopyable & );
Uncopyable &operator=( const Uncopyable & );
};
class HomeForSale : private Uncopyable {
int a = 0;
};
int main()
{
HomeForSale s1;
HomeForSale s2( s1 );
s1 = s2;
return 0;
}
|
Using delete and default.
| class HomeSale {
public:
HomeSale() = default;
HomeSale( const HomeSale & ) = delete;
HomeSale &operator=( const HomeSale & ) = delete;
private:
int a = 0;
};
int main()
{
HomeSale s1;
HomeSale s2( s1 );
s1 = s2;
return 0;
}
|
Item 7: Declare destructors virtual in polymorphic base classes.
The implementation of virtual functions requires that objects carry information that can be used at runtime to determine which virtual functions should be invoked on the object. this information typically takes the form of a pointer called a vptr(“virtual table pointer”). The vptr points to an array of function pointers called a vtbl(“virtual table”); each class with virtual functions has an associated vtbl. When a virtual function is invoked on an object, the actual function called is determined by following the object’s vptr to a vtbl and then looking up the appropriate function pointer in the vtbl.
The bottom line is that gratuitously declaring all destructors virtual is just as wrong as never declaring them virtual.
Things to Remember
Polymorphic base classes should declare virtual destructors. If a class has any virtual functions, it should have a virtual destructor.
Classes not designed to be base classes or not designed to be used polymorphically should not declare virtual destructors.
Item 8: Prevent exceptions from leaving destructors.
Things to Remember
Destructors should never emit exceptions. If functions called in a destructor may throw, the destructor should catch any exceptions, then swallow them or terminate the program.
If class clients need to be able to react to exceptions thrown during an operation, the class should provide a regular function that performs the operation.
Item 9: Never call virtual functions during construction or destruction.
During base class construction, virtual functions never go down into derived classes.
An Object doesn’t become a derived class object until execution of a derived class contruction begins.
Upon entry to the base class destructor, the object becomes a base class object.
The only way to avoid this program is to make sure that none of your constructors or destructors call virtual functions on the object beging created or destroyed and that all the functions they call obey the same constraint.
Things to Remember
Don't call virtual functions during construction or destruction, because such calls will never go to a more derived class than that of the currently executing constructor or destructor.
Item 10: Have assignment operators return a reference to *this.
Things to Remember
Have assignment operators return a reference to *this.
Item 11: Handle assignment to self in operator=.
Four steps to improve the operator=.
| Widget &Widget::operator=( const Widget &rhs )
{
delete pb;
pb = new Bitmap( *rhs.pb );
return *this;
}
Widget &Widget::operator=( const Widget &rhs )
{
if( this != &rhs )
{
delete pb;
pb = new Bitmap( &rhs.pb );
}
return *this;
}
Widget &Widget::operator=( const Widget &rhs )
{
Bitmap *pOrig = pb;
pb = new Bitmap( *rhs.pb );
delete pOrig;
return *this;
}
Widget &Widget::operator=( const Widget &rhs )
{
Widget temp( rhs );
swap( temp );
return *this;
}
|
Things to Remember
Make sure operator= is well-behaved when an object is assigned to itself. Techniques include comparing addresses of source and target objects, careful statement ordering, and copy-and-swap.
Make sure that any function operating on more than one object behaves correctly if two or more of the objects are the same.
Item 12: Copy all parts of an object.
Things to Remember
Copying functions should be sure to copy all of an object's data members and all of its base class parts.
Don't try to implement one of the copying functions in terms of the other. Instead, put common functionality in a third function that both call.
Item 13: Use objects to manage resources.
by putting resources inside objects, we can rely on C++’s automatic destructor invocation to make sure that the resources are released.
RAII: Resource Acquistion Is Initialization
Things to Remember
To prevent resources leaks, use RAII objects that acquire resources in their constructors and release them in their destructors.
Two commonly useful RAII classes are tr1::shared_ptr and auto_ptr. tr1::shared_ptr is usually the better choice, because its behavior when copied is intuitive. Copying an auto_ptr sets it to null.
Item 14: Think carefully about copying behavior in resource-manageing classes.
Copy the underlying resource.
Transfer ownership of the underlying resource.
Things to Remember
Copying an RAII object entails copying the resource it manages, so the copying behavior of the resource determines the copying behavior of the RAII object.
Common RAII class copying behaviors are disallowing copying and performing reference counting, but other behaviors are possible.
Item 15: Provide access to raw resources in resource-manageing classes.
Things to Remember
APIs often require access to raw resources, so each RAII class should offer a way to get at the resource it manages.
Access may be via explicit conversion or implicit conversion. In general, explicit conversion is safer, but implicit conversion is more convenient for clients.
Item 16: Use the same form in corresponding uses of new and delete.
When you employ a new expression, two things happen: 1. memory is allocated( operator new ); 2. one or more constructors are called for that memory.
When you employ a delete expression, two other things happen: one or more destructors are called for the memory, then the memory is deallocated( operator delete ).
Things to Remember
If you use [] in a new expression, you must use [] in the corresponding delete expression. If you don't use [] in a new expression, you mustn't use [] in the corresponding delete expression.
Item 17: Store newed objects in smart pointers in standalone statements.
Things to Remember
Store newed objects in smart pointers in standalone statements. Failure to do this can lead to subtle resource leaks when exceptions are thrown.
Item 18: Make interfaces easy to use correctly and hard to use incorrectly.
unless there’s a good reason not to, have your types behave consistently with the built-in types.
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, etc.
Item 19: Treat class design as type design.
- How should objects of your new type be created and destroyed?
- How should object initialization differ from object assignment?
- What does it mean for objects of your new type to be passed by value?
- What are the restrictions on legal values for your new type?
- Does your new type fit into an inheritance graph?
- What kind of type conversion are allowed for your new type?
- What operators and functions make sense for the new type?
- What standard functions should be disallowed?
- Who should have access to the members of your new type?
- What is the “undeclared interface” of your new type?
- How general is your new type?
- Is a new type really what you need?
Things to Remember
Class design is type design. Before defining a new type, be sure to consider all the issues discussed in this Item.
Item 20: Prefer pass-by-reference-to-const to pass-by-value.
references are typically implemented as pointers, so passing something by reference usually means really passing a pointer.
Things to Remember
Prefer pass-by-reference-to-const over pass-by-value. It's typically more efficent and it avoids the slicing problem.
The rule doesn't apply to built-in types and STL iterator and function object types. For them, pass-by-value is usually appropriate.