Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
programming:improve-your-cpp [2006/12/14 14:47]
cyril
programming:improve-your-cpp [2013/09/19 16:40] (current)
Line 1: Line 1:
-<php>$conf['maxtoclevel'] = 3;</php> +\\ This page is based on the excellent books of Scott Meyers : __Effective C++ (50 specific ways to improve your programs and designs)__ and __More effective C++ (35 new ways to improve your programs and designs)__.
- +
-\\ This page is based on the excellent books __50 ways to improve your C++__ and __35 more ways to improve your C++__.+
  
 ====== Effective C++ ====== ====== Effective C++ ======
  
 ===== Shifting from C to C++ ===== ===== Shifting from C to C++ =====
- 
  
 === Item 1 : Use const and inline instead of #define === === Item 1 : Use const and inline instead of #define ===
-Because constants don't appear in the symbole table which is annoying for debugging :+Because constants don't appear in the symbole table which is annoying for debugging, use constant variables (it is as fast) :
 <code cpp> <code cpp>
 const float ASPECT_RATIO = 1.653; const float ASPECT_RATIO = 1.653;
 </code> </code>
-And because there are lot of traps with macros :+And because there are lot of traps with macros, use inline (templated if necessary) :
 <code cpp> <code cpp>
 template<class T> inline T& MAX(T& a, T& b) { return a>b ? a : b; } template<class T> inline T& MAX(T& a, T& b) { return a>b ? a : b; }
 </code> </code>
 +
 +Main traps of macros are :
 +  * if you forget to put parentheses around all variable names :
 +<code cpp>
 +#define sqr(x) x*x // wrong way
 +sqr(1+2) == 1+2*1+2 == 1+2+2 == 5 != 9
 +#define sqr(x) (x)*(x) // right way
 +</code>
 +  * if you call a function as a macro parameter (''sqr(s.toInt())''), the function call will be done two times
 +
 +Inline functions have none of these traps, and are as fast.
  
 === Item 2 : Prefer iostream to stdio.h === === Item 2 : Prefer iostream to stdio.h ===
-Because of type safety an extensiblity. To define stream operators in your class :+Because of type safety and extensiblity.  
 + 
 +To define stream operators in your class :
 <code cpp> <code cpp>
 friend ostream& operator<<(ostream& s, const ComplexInt& c); friend ostream& operator<<(ostream& s, const ComplexInt& c);
Line 35: Line 45:
  
 === Item 5 : Use the same form in corresponding calls to new and delete === === Item 5 : Use the same form in corresponding calls to new and delete ===
-''new'' with ''delete'', and ''new[]'' with ''delete[]'', or memory leaks ! (don't call all destructors)+''new'' with ''delete'', and ''new[]'' with ''delete[]'', or memory leaks ! (delete with new[] don't call all destructors)
  
 === Item 6 : Call delete on pointer members in destructors === === Item 6 : Call delete on pointer members in destructors ===
-Initialize all pointer members in each of the constructors (at ''NULL'' if not allocated) ;\\ +  * Initialize all pointer members in each of the constructors (at ''NULL'' if not allocated)  
-Delete for all pointer members the existing memory and assign new memory in the assignment operator ; \\ +  Delete for all pointer members the existing memory and assign new memory in the assignment operator 
-Delete the memory in the destructor+  Delete the memory in the destructor
  
  
Line 164: Line 174:
  
 === Item 11 : Define a copy constructor and an assignment operator for classes with dynamically allocated memory === === Item 11 : Define a copy constructor and an assignment operator for classes with dynamically allocated memory ===
 +Because if you don't, the compiler with use the default ones which are bit-to-bit copies. Thus it will copy pointers, and if you free it in one object, the other one will point to nothing ...
 +
  
 === Item 12 : Prefer initialization to assignment in constructors === === Item 12 : Prefer initialization to assignment in constructors ===
Line 169: Line 181:
  
 === Item 13 : List members in an initialization list in the order in which they are declared === === Item 13 : List members in an initialization list in the order in which they are declared ===
-Because it is the order of the declarations which is followed by the compiler, not the order of the initialization list, so you can fall into traps if you don't have the same order.+Because it is always the order of the declarations which is followed by the compiler,  
 +and never the order of the initialization list,  
 +so you can fall into traps if you don't have the same order (eg you initialize ''size'', then initialize data with ''new int[size]'', but if ''data'' is declared before ''size'' the ''new'' will be done before the initialization of ''size''...)
  
 === Item 14 : Make destructors virtual in base classes === === Item 14 : Make destructors virtual in base classes ===
-Because if you don't, if you create a derived object and put it in a pointer of the base class, when you will delete it it won't call the destructor of the derived class ! But only for base class (if you plan to inheritate from it), which generally corresponds to the fact that there is at least one virtual function, because you loose a little performance. Tip : if you want to have an abstract class, but you don't have any function to make pure virtual, declare a pure virtual destructor ! (but you have nevertheless to provide a definition for this pure virtual destructor, and do not declare it inline because it could have problems with the virtual thing).+Because if you don't, when you create a derived object and put it in a pointer of the base class, and then delete itit won't call the destructor of the derived class !  
 + 
 +But do it only if it is a base class (you plan to inheritate from it), which generally corresponds to the fact that there is at least one virtual function, because you loose a little performance.  
 + 
 +Tip : if you want to have an abstract class, but you don't have any function to make pure virtual, declare a pure virtual destructor ! (but you have nevertheless to provide a definition for this pure virtual destructor, and you can'declare it inline).
  
 === Item 15 : Have operator= return a reference to *this === === Item 15 : Have operator= return a reference to *this ===
-To allow chains assignments :+To allow to chain assignments ''a=b=c=0'' :
 <code cpp> <code cpp>
 C& C::operator=(const C&); C& C::operator=(const C&);
Line 186: Line 204:
  
 === Item 17 : Check for assignment to self in operator= === === Item 17 : Check for assignment to self in operator= ===
-That's to say, always begin by : ''if (this == &rhs) return *this;''Not only because it saves time, but above all because it will create serious problems with freeing and reallocating resources ! But be careful this test won't work with multiple inheritance because one same object can have different addresses according to the type it is casted to. If so you could add an unique identifier for each object.+That's to say, always begin by :  
 +<code cpp>if (this == &rhs) return *this;''</code> 
 + 
 +Not only because it saves time, but above all because it will create serious problems with freeing and reallocating resources !  
 + 
 +But be careful this test won't work with multiple inheritance because one same object can have different addresses according to the type it is casted to. If so you could add an unique identifier for each object.
  
  
Line 193: Line 216:
  
 === Item 18 : Strive for class interfaces that are complete and minimal === === Item 18 : Strive for class interfaces that are complete and minimal ===
 +Because clients can do whatever they want to do, but it remains easy to learn (no confusion), easier to maintain, and shorter to compile. In brief, think carefully about whether the convenience of a new function justifies the additional costs.
 +
  
 === Item 19 : Differentiate among member functions, global functions, and friend functions === === Item 19 : Differentiate among member functions, global functions, and friend functions ===
 +When in doubt, try to be object-oriented (overloading operators etc). 
 +
 +But for example if you create a Fraction class and want to implement multiplication. If you overload ''operator*'' as a member, ''x*2'' will work, because the compiler know how to cast ''2'' into a Fraction thanks to your constructor, but ''2*x'' will fail because it can only do that on parameters. So if you want a commutative multiplication, you have to declare ''operator*'' as a global function taking two fractions. And if it needs to access protected members you have to declare it as a friend of Fraction.
 +
 +In the same way, stream operators are always global function, because if they were members the variable should be on the left, like ''s >> cin'', what is contrary to the convention and would confuse everyone.
  
 === Item 20 : Avoid data members in the public interface === === Item 20 : Avoid data members in the public interface ===
 +Because :
 +  * it is simplier for the user (all members are functions, so there no need to try to remember whether to use parentheses), 
 +  * it gives you control over the accessibility of the members,
 +  * it gives you functional abstraction (you can change a data member by computations for example).
 +
  
 === Item 21 : Use const whenever possible === === Item 21 : Use const whenever possible ===
  
-=== Item 22 :  ===+You can use it outside of classes for : 
 +  * global constants 
 +  * static objects (local to either a file or a block) 
 +Inside classes for : 
 +  * static data members 
 +  * non static data members 
 +With pointers : 
 +<code cpp> 
 +char       *p        "Hello"; // non-const pointer, non-const data 
 +const char *p        "Hello"; // non-const pointer,     const data 
 +char       * const p "Hello"; //     const pointer, non-const data 
 +const char * const p = "Hello"; //     const pointer,     const data 
 +</code> 
 +And of in function declarations : 
 +  * individual parameters 
 +  * return value 
 +  * function as a whole for member functions (doesn't modify the object, excluding static members : can be invoked on a ''const'' object). Functions differing only in their constness can be overloaded.
  
-=== Item 23 :  === 
  
-=== Item 24 :  === 
  
-=== Item 25 :  === 
  
-=== Item 26  ===+C++ makes a bitwise test for const member functions it checks it doesn't modify any member. But this is not perfect, because you can however modify a const object, eg if a const function returns a pointer to a member (the member can be modified outside).
  
-=== Item 27 :  ===+Moreover sometimes you can want to modify a member in a function, but this function is const in the sense that the modifications are undetectable by a client (eg if you want to cache the length of a string, you don't modify the string so the function is conceptually const, but you modify the cache variable so the compiler won't accept it). Fortunately, you can cast away constness (''this'' is viewed as ''C * const this;'' for non-const member functions, and as ''const C * const this;'' for const member functions) : 
 +<code cpp> 
 +unsigned String::length() const 
 +
 +  String * const localThis (String * const) this; 
 +  dataLength ...; 
 +  ... 
 +
 +</code>
  
-=== Item 28  ===+=== Item 22 Pass and return objects by reference instead of by value === 
 +Problems of returning by value : 
 +  * it can cost a lot of time because copy constructors of the object and all sub-objects are called for each copy 
 +  * the //slicing problem// can be dangerous : if you have a base class with a virtual function, and a derivated class, if you pass a derivated object to a function that takes a base object by value, it will lose all virtual functions of the derivated object because it is the copy constructor of the base class which is called !
  
-=== Item 29  ===+=== Item 23 Don't try to return a reference when you must return an object === 
 +If you have to return a new object (eg for operator+), you must return it by value. Indeed this new object will be created either in the stack and disappear when you go out of the function (=> segfault), or in the heap (with ''new'') but then you don't know who should delete it and there will be memory leaks.
  
-=== Item 30  ===+=== Item 24 Choose carefully between function overloading and parameter defining === 
 +Both permit to call functions with a different number of parameters. Parameter defining (default values) makes easier to avoid duplication of code. But you can only use it if : 
 +  * a reasonable default value exists (and if a generic algorithm exists and is not less efficient that what you could have done by knowing the number of arguments), or 
 +  * you can use a //magic number// which tells that the parameter is not set. 
 +To cope with the duplication code problem with overloading, you can either call another overloaded function, or create a common underlying private member function that do the common work.
  
-=== Item 31  ===+=== Item 25 Avoid overloading on a pointer and a numerical type === 
 +ie avoid : 
 +<code cpp> 
 +void f(int x); 
 +void f(char *p); 
 +</code> 
 +Because there is no ambiguity for the compiler when calling ''f(0)'' (it will call ''f(int)''  because 0 is an int), but there is an ambiguity for the programmer. Indeed ''f(NULL)'' will call ''f(int)'' also because ''NULL'' is most often defined by ''#define NULL 0'', and there is no way to define it as a pointer which works with all pointer types without need for cast.
  
-=== Item 32  ===+=== Item 26 Guard against potential ambiguity === 
 +This code is correct and compiles : 
 +<code cpp> 
 +class A {  
 + public: 
 +  A(const class B&); // constructor from B 
 +}; 
 +class B { 
 + public: 
 +  operator A() const; // cast operator to A 
 +}; 
 +</code> 
 +But it can create ambiguities : 
 +<code cpp> 
 +void g(const A&); 
 +B b; 
 +g(b); // error ! - ambiguous : calls constructor from B or cast to A ? 
 +</code> 
 +The problem with ambiguities is that you can miss it at the beginning, and if it is the client who is faced to and if he doesn't have the source code, he may have no way around the problem (note : maybe ''g(A(b))'', but it will cost a constructor call). 
 +This can happened also with ''f(int)'' and ''f(char)'' called with ''double'', or with multiple inheritance : 
 +<code cpp> 
 +class Base1 { 
 + public: 
 +  int doIt(); 
 +}; 
 +class Base2 { 
 + public: 
 +  void doIt(); 
 +}; 
 +class Derived : Base1, Base2 {} d; 
 +d.doIt() // error - ambiguous 
 +</code> 
 +You have to explicitly specify which one of the functions you want to call : ''d.Base1::doIt()''. Even modifying accessibility (one private and the other one public) won't change anything, and that's normal because otherwise changing accessibility would change the function which is called by the same code, and that's very bad !
  
-=== Item 33  ===+=== Item 27 Explicitly disallow use of implicitly generated member functions you don't want === 
 +The compiler automatically generates some functions such as ''operator='' and copy constructor if you don't provide it. So you want to forbid them, you have to declare them private, and  to not define them (for member and friend functions) : it will create a link error if someone tries to use it, even implicitly.
  
-=== Item 34 :  === 
  
-=== Item 35  ===+=== Item 28 Use structs to partition the global namespace === 
 +Define your global constants in a struct, to avoid clashes between libraries (note : why not using namespaces ??)
  
-=== Item 36  ===+===== Classes and Functions Implementation =====
  
-=== Item 37 :  === 
  
-=== Item 38 :  === 
  
-=== Item 39 :  === 
  
-=== Item 40  ===+=== Item 29 Avoid returning "handles" to internal data from const member functions === 
 +A handle is a pointer or a reference. If you do so, then it is possible to modify a const object using the handle returned by the const member function ! One way to go around it could be to return the result by value, or to return a handle on a copy of the data, but it costs time, and creating copies of objects lead to risks of memory leak. Another way is to return a const pointer or reference : it is both fast and safe, but it is not the same thing and may restrict callers unnecessarily (note: I don't agree with these drawbacks, the user can do a copy himself if he need to modify it).
  
-=== Item 41  ===+=== Item 30 Avoid member functions that return pointers or references to members less accessible than themselves === 
 +Because if you do so, you change the access level of the returned member (so what was the matter to give it this access level). If you have no choice, try at least to return a const handle.
  
-=== Item 42  ===+=== Item 31 Never return a reference to a local object or a dereferenced pointer initialized by new within the function === 
 +If you return a reference to a local object, as the object disappears when going out the function, this is automatic segmentation fault when using it. If you return a pointer initialized by a new within the function, you have to ask the user to delete it, but it is not reasonable, and sometimes impossible because of temporary objects (for example the result of operator + in ''s=a+b+c''). (note: in some cases, you can try to put the result in a class member and return reference to it, if you want to avoid the creation of a new object).
  
-=== Item 43  ===+=== Item 32 Use enums for integral class constants === 
 +If you want to use different constants for classes, you can't use a global const variable or a ''#define'' because it can't be different for different classes, neither use a const member because it must be initialized outside the declaration of the class thus is not known at compilation time. The solution is to use enum : 
 +<code cpp> 
 +class X { 
 +  enum { BUFSIZE=100 }; 
 + char buffer[BUFSIZE]; 
 +
 +</code>
  
-=== Item 44  ===+=== Item 33 Use inlining judiciously === 
 +Inlining avoid the cost of a function call, but it can increase dramatically the size of the code, what can be a problem on systems with limited memory, and can slow a lot the program by leading to pathological paging behavior on systems with virtual memory. 
  
-=== Item 45  ===+But there is more. The inline directive is just a request, and the compiler can decide to not inline the function (for example if it is recursive, or too long). When it is not inlined, as it must be defined in the header, the compiler will declare it static in order to avoid linking problems when several source files include the same header. Then there will be several copies of the code (and you still pay the cost of function calls). Moreover if you ask somewhere the address of the function, it will also create the body of the function. And debuggers cannot go through inline functions (notewith VisualStudio and g++, it is just disabled in debug mode ...).
  
-=== Item 46 :  ===+A good methodology with inline functions is to inline only obvious functions at the beginning (getters&setters), then try to find which functions are called often, and try to inline them checking in the warning messages of the compiler that it is really inlined.
  
-=== Item 47  ===+=== Item 34 Minimize compilation dependencies between files === 
 +C++ doesn't really separate interface from implementation, because private stuff is declared with public stuff. Thus if you modify the implementation of a class, all files which use this class will be recompiled, even if the interface which is the only important thing didn't change. And for big projects it can be painful.
  
-=== Item 48 :  ===+There are two solutions for separating interface from implementation : 
 +  * Put all private stuff in another class (implementation class), and put a pointer to this class in the main class (interface class). The cost is one level of indirection for access to private (implementation) stuff. 
 + * Make the interface class an abstract base class which contains public stuff (only pure virtual functions), and inheritate from it an implementation class. The cost is virtuality : one indirection for each function call, and more important you lose inlining (but this is also true for the previous solution). 
 +But don't dismiss these methods because they have a cost, use them during development to minimize the impact on clients when implementation change, and replace interface and implementation classe with one concrete for production use if there is a real difference in speed or size.
  
-=== Item 49 :  ===+===== Inheritance and Object-Oriented design =====
  
-=== Item 50 :  ===+ 
 +=== Item 35 : Make sure public inheritance models "isa" === 
 + 
 +=== Item 36 : Differentiate between inheritance of interface and inheritance of implementation === 
 + 
 +=== Item 37 : Never redefine an inherited nonvirtual function === 
 + 
 +=== Item 38 : Never redefine an inherited default parameter value === 
 + 
 +=== Item 39 : Avoid casts down the inheritance hierarchy === 
 + 
 +=== Item 40 : Model "has-a" or "is-implemented-in-terms-of" through layering === 
 + 
 +=== Item 41 : Use private inheritance judiciously === 
 + 
 +=== Item 42 : Differentiate between inheritance and templates === 
 + 
 +=== Item 43 : Use multiple inheritance judiciously === 
 + 
 +=== Item 44 : Say what you mean ; understand what you're saying === 
 + 
 +===== Miscellany ===== 
 + 
 + 
 +=== Item 45 : Know what functions C++ silently writes and calls === 
 + 
 +=== Item 46 : Prefer compile-time and link-time errors to runtime errors === 
 + 
 +=== Item 47 : Ensure that global objects are initialized before they're used === 
 + 
 +=== Item 48 : Pay attention to compile warnings === 
 + 
 +=== Item 49 : Plan for coming language features === 
 + 
 +=== Item 50 : Read the ARM ===
  
  
Line 286: Line 435:
 That's to say don't place derived class objects in an array of base class pointers, if they don't have the same size, because indexing will use size of the base class. So problems when you write your loop, or when you delete the array (the compiler generates a loop, and it is said in the language specification that the result is undefined). That's to say don't place derived class objects in an array of base class pointers, if they don't have the same size, because indexing will use size of the base class. So problems when you write your loop, or when you delete the array (the compiler generates a loop, and it is said in the language specification that the result is undefined).
  
-=== Item 4 :  ===+=== Item 4 : Avoid gratuitious default constructors ===
  
 ===== Operators ===== ===== Operators =====
  
-=== Item 5 :  ===+=== Item 5 : Be wary of user-defined conversion functions ===
  
-=== Item 6 :  ===+=== Item 6 : Distinguish between prefix and postfix forms of increment and decrement operators ===
  
-=== Item 7 :  ===+=== Item 7 : Never overload &&, || or , ===
  
-=== Item 8 :  ===+=== Item 8 : Understand the different meanings of new and delete ===
  
 ===== Exceptions ===== ===== Exceptions =====
-=== Item 9 :  ===+=== Item 9 : Use destructors to prevent resource leaks ===
  
-=== Item 10 :  ===+=== Item 10 : Prevent resource leaks in constructors ===
  
-=== Item 11 :  ===+=== Item 11 : Prevent exceptions from leaving destructors ===
  
-=== Item 12 :  ===+=== Item 12 : Understand how throwing and exception differs from passing a parameter or calling a virtual function ===
  
-=== Item 13 :  ===+=== Item 13 : Catch exceptions by reference ===
  
-=== Item 14 :  ===+=== Item 14 : Use exception specifications judiciously ===
  
-=== Item 15 :  ===+=== Item 15 : Understand the costs of exception handling ===
  
 ===== Efficiency ===== ===== Efficiency =====
-=== Item 16 :  ===+=== Item 16 : Remember the 80-20 rule ===
  
-=== Item 17 :  ===+=== Item 17 : Consider using lazy evaluation ===
  
-=== Item 18 :  ===+=== Item 18 : Amortize the cost of expected computations ===
  
-=== Item 19 :  ===+=== Item 19 : Understand the origin of temporary objects ===
  
-=== Item 20 :  ===+=== Item 20 : Facilitate the return value optimization ===
  
-=== Item 21 :  ===+=== Item 21 : Overload to avoid implicit type conversions ===
  
-=== Item 22 :  ===+=== Item 22 : Consider using op= instead of stand-alone op ===
  
-=== Item 23 :  ===+=== Item 23 : Consider alternative libraries ===
  
 === Item 24 : Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI === === Item 24 : Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI ===
Line 343: Line 492:
  
 ===== Techniques ===== ===== Techniques =====
 +
 +=== Item 25 : Virtualizing constructors and non-member functions ===
 +
 +=== Item 26 : Limiting the number of objects of a class ===
 +
 +=== Item 27 : Requiring or prohibiting heap-based objects ===
 +
 +=== Item 28 : Smart pointers ===
 +
 +=== Item 29 : Reference counting ===
 +
 +=== Item 30 : Proxy classes ===
 +
 +=== Item 31 : Making functions virtual with respect to more than one object ===
 +
 +===== Miscellany =====
 +
 +
 +=== Item 32 : Program in the future tense ===
 +
 +=== Item 33 : Make non-leaf classes abstract ===
 +
 +=== Item 34 : Understand how to combine C++ and C in the same program ===
 +
 +=== Item 35 : Familiarize yourself with the language standard ===
 +
  
programming/improve-your-cpp.1166107622.txt.gz · Last modified: 2013/09/19 16:43 (external edit)
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0