1 / 96

Effective C++

Effective C++. 55 Specific Ways to Improve Your Programs and Designs. Why???. “Used without discipline, however, C++ can lead to code that is incomprehensible, unmaintainable, inextensible, inefficient and just plain wrong.”. What’s it all about?. Decisions, Decisions, Decisions.

justin
Download Presentation

Effective C++

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Effective C++ 55 Specific Ways to Improve Your Programs and Designs

  2. Why??? “Used without discipline, however, C++ can lead to code that is incomprehensible, unmaintainable, inextensible, inefficient and just plain wrong.”

  3. What’s it all about? Decisions, Decisions, Decisions inheritance or templates? public or private inheritance? private inheritance or composition? member or non-member functions? DESIGN PATTERNS!

  4. What’s it all about? (2nd category) How do I do this correctly? should destructor be virtual? what return type to use? what should operator do when it can’t get enough memory?

  5. Terminology • Declarations – name & type extern int x; // object declaration // function declaration, also signature std::size_t numDigits(int num); class Widget; // class declaration // template declaration template<typename T> class GraphNode;

  6. Terminology (continued) • Definition – details int x; // object definition – memory // function definition - body std::size_t numDigits(int num) { … } // class and template definitions, include // methods and data class Widget{ public: // list of methods, data… }; template <typename T> class GraphNode { public: // list of methods, data .. };

  7. Terminology (continued) • Initialization – give first value • Default constructor – no arguments (may be constructor with all default arguments) • Recommendation: make constructor explicit, to avoid implicit type conversions (example next slide)

  8. class A{ public: A(); }; class B{ public: explicit B(int x=0, bool b=true); }; class C{ public: explicit C(int x); // not a default // constructor }; void doSomething(B bObj); // in main… B bObj1; doSomething(bObj1); // fine B bObj2(28); doSomething(bObj2); // fine doSomething(28); // error! doSomething(B(28)); // fine, uses B constructor // explicitly Explicit Example

  9. Copy Constructor/Copy Assignment class Widget { public: Widget(); Widget(const Widget& rhs); Widget& operator =(const Widget& rhs); … }; Widget w1; // invoke default constructor Widget w2(w1); // invoke copy constructor w1 = w2; // invoke assignment Widget w3 = w2; // invoke copy constructor! Why?

  10. Item #1 View C++ as a federation of languages: • C – blocks, statements, preprocessor, arrays, pointers, etc. • C++ - OO, classes, encapsulation, inheritance, polymorphism, virtual functions • Template C++ - generic programming, often with special rules • STL – containers, iterators, algorithms, function objects, using templates and specific conventions Rules for effective C++ vary, depending on the part of C++ you are using.

  11. And now… On to some items!

  12. Item #2 Prefer consts, enums and inlines to #defines (i.e., prefer compiler to preprocessor) #define ASPECT_RATIO 1.653; const double AspectRatio = 1.653; • Symbolic names may be removed, don’t show up in error messages or debugging – confusing. • Could have multiple copies of 1.653 in object code.

  13. More on constants class CostEstimate { private: static const double FudgeFactor; … }; const double CostEstimate::FudgeFactor = 1.35; class GamePlayer { private: enum {NumTurns = 5; } int scores[NumTurns]; }; remember static? some compilers won’t allow values in “declaration” – must provide definition enum “hack” – if need value for constant and compiler won’t allow

  14. Item #3 Use const wherever possible char greeting[] = “Hello”; char *p = greeting; // non-const pointer, non-const data const char *p2 = greeting; // non-const pointer, const char data char * const p3 = greeting; // const pointer, non-const data const char * const p4 = greeting; // const pointer, const data

  15. Placement of const • The following are equivalent: void f1 (const Widget *pw); void f2 (Widget const *pw); • Iterators are similar to pointers: • a const_iterator is like const T*… iterator can point to a different item, but item can’t be changed • a const iterator is like T *const … iterator can’t point to a different item, but you can change the value of the item that it is pointing to

  16. Consider making return value const const Rational { . . . const Rational operator *(const Rational& lhs, const Rational& rhs); }; Rational a, b, c; if (a * b = c) . . . Meant to be == What will happen with const? without? What happens with built-in types?

  17. May need two versions • Overloaded [] operator, notice return by &, to allow modification • Can’t overload based on return type, but can overload based on const vs. non-const member function class TextBlock { public: . . . const char& operator[](std::size_t pos) const { return text[pos]; } char& operator[](std::size_t pos) { return text[pos]; } private: std::string text; }; void print(const TextBlock& ctb) { std::cout << ctb[0]; // OK ctb[0] = ‘A’; // Not OK – compiler error } TextBlock tb(“hello”); tb[0] = ‘H’; // OK because return has &, not const This is required to overload

  18. Physical constness vs Logical constness • Physical (bitwise) const: member function is const iff it doesn’t modify any of the bits inside the object • Logical const: const member method might modify some bits in object, but only in ways clients cannot detect • Compilers enforce bitwise constness, you should program using logical constness

  19. Example: Problem with bitwise const class CTextBlock { public: … char& operator[](std::size_t pos) const {return pText[pos]; } private: char *pText; }; const CTextBlock cctb(“Hello”); // constant object char *pc = &cctb[0]; // calls constant [] operator *pc = ‘J’; // cctb is now “Jello” violates logical constness, but compiler allows!

  20. Modifying bits client doesn’t see class CTextBlock { public: . . . std::size_t length() const; private: char *pText; std::size_t textLength; // last calculated length bool lengthIsValid; // whether length is valid }; std::size_t CTextBlock::length() const { if (!lengthIsValid) { // error! changes bits textLength = std::strlen(pText); lengthIsValid = true; } return textLength; }

  21. Mutable to the rescue class CTextBlock { public: . . . std::size_t length() const; private: char *pText; mutable std::size_t textLength; mutable bool lengthIsValid;}; std::size_t CTextBlock::length() const { if (!lengthIsValid) { textLength = std::strlen(pText); // OK now lengthIsValid = true; } return textLength; }

  22. Exercise • Begin C++ Exercise #1

  23. Avoid duplication in const/non-const class TextBook { public: … const char& operator[](std::size_t pos) const { … // do bound checking … // log access data … // verify data integrity return text[pos]; } char& operator[](std::size_t pos) { … // do bound checking … // log access data … // verify data integrity return text[pos]; } private: std::string text; }; lots of duplicate code! could put duplicated code in a function and call it – but then have duplicated calls to that function, and duplicated return

  24. Cast-Away const class TextBook { public: … const char& operator[](std::size_t pos) const { … // same as before return text[pos]; } char& operator[](std::size_t pos) { return const_cast<char&>( static_cast<const TextBlock&>(*this)[position]); } private: std::string text; }; Now non-const [] just calls const const_cast needed to remove const before return add const, to call const version of [] safe conversion, so use static_cast casts covered in Item 27 DO NOT go the other direction – not safe!

  25. Item #4 Make sure that objects are initialized before they’re used. The rules for when object initialization is guaranteed to take place are too complicated to be worth memorizing. Better practice to always initialize objects before use.

  26. Initialization • Make sure all constructors initialize everything in the object. • Assignment is not the same as initialization. ABEntry::ABEntry(const std::string&name, const std::list<PhoneNumber>& phones) { theName = name; thePhones = phones; numTimesConsulted = 0; } default constructors were called for these prior to entering the body of the constructor – that’s when they were initialized. Not true for built-in types (e.g., numTimesCalled).

  27. Initialization (continued) • Prefer member initialization lists: ABEntry::ABEntry(const string& name, const list<PhoneNumber>& phones) : theName(name), thePhones(phones), numTimesConsulted (0) {} • Single call to copy constructor is more efficient than call to default constructor followed by call to copy assignment. • No difference in efficiency for numTimesConsulted, but put in list for consistency

  28. Initialization (continued) • Can do member initialization lists even for default construction: ABEntry::ABEntry() : theName(), thePhones(), numTimesConsulted (0) {} • Members are initialized in the order they are listed in class. Best to list them in that order in initialization list. • Base classes are always initialized before subclasses.

  29. Initialization of non-local static objects • Better to convert them to local static objects. class FileSystem { public: std::size_t numDisks() const; . . . }; extern FileSystem tfs; // declaration, must be defined // in some .cpp in your library class Directory{ public Directory(params); }; Directory::Directory(params) { … std::size_t disks = tfs.numDisks(); // use tfs object } Has tfs been initialized?

  30. Initialization of non-local (continued) class FileSystem { … } // as before FileSystem& tfs() { static FileSystem fs; return fs; } class Directory{ … } // as before Directory::Directory(params) { … std::size_t disks = tfs().numDisks(); // calls tfs function now } SINGLETON DESIGN PATTERN non-local static objects replaced with local static object clients call functions instead of referring to object must ensure only one copy of FileSystem object (protected constructor)

  31. On to… Chapter Two Constructors, Destructors and Assignment Operators

  32. Item #5 Know what functions C++ silently writes and calls. class Empty{}; becomes: class Empty{ public: Empty() { ... } Empty(const Empty& rhs) { … } ~Empty() { … } Empty& operator=(const Empty& rhs) {…} };

  33. What do they do? • Copy constructor and assignment generally do a field-by-field copy. • These functions will not be written if your class includes a const value or a reference value (compiler isn’t sure how to handle). template <typename T> class NamedObject { public: NamedObject(std::string& name, const T& value); private: std::string& nameValue; const T objectValue; };

  34. Item #6 Explicitly disallow the use of compiler-generated functions you do not want • By declaring member functions explicitly, you prevent compilers from generating their own version. • By making a function private, you prevent other people from calling it. – don’t define them, so anyone who tries will get a linker error • Even better, put functions in parent class, if child class attempts to call will generate a compiler error (earlier detection is better).

  35. Example class Uncopyable { protected: Uncopyable(); ~Uncopyable(); private: Uncopyable(const Uncopyable&); Uncopyable& operator=(const Uncopyable&); }; class HomeForSale: private Uncopyable { … // class has no copy ctor or = operator };

  36. Item #7 Declare destructors virtual in polymorphic base classes. class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); … }; class AtomicClock : public TimeKeeper { … }; class WristWatch : public TimeKeeper { … }; TimeKeeper* getTimeKeeper(); // returns pointer to dynamically allocated object TimeKeeper *ptk = getTimeKeeper(); // AtomicClock … // use it in some way delete ptk; // release it THE RESULTS OF THIS OPERATION ARE UNDEFINED!

  37. TimeKeeper continued • Most likely AtomicClock part of object would not be destroyed – a “partially destroyed” object • Solution: class TimeKeeper { public: TimeKeeper(); virtual ~TimeKeeper(); … }; • Any class with virtual functions should almost certainly have a virtual destructor.

  38. Don’t always make it virtual… • If a class does not contain any virtual functions, often indicates its not intended to be a base class. Making destructor virtual would be a bad idea. class Point { public: Point(int xCoord, int yCoord); ~Point(); private: int x, y; }; • Point class can fit in 64-bit register, be passed as 64-bit value to other languages (C/Fortran) • Virtual functions require objects to carry extra info for runtime binding. Typically a vptr (virtual table pointer) that points to an array of function pointers called a vtbl (virtual table). • Point class will now be 96 bits (on 32-bit architecture) Handy rule: declare a virtual destructor if and only if that class contains at least one other virtual function.

  39. When not to extend… • Be careful when you choose to extend a class. std::string contains no virtual functions, so is not a good choice for a base class. STL container types also do not have virtual destructors. class SpecialString : public std::string { … }; SpecialString *pss = new SpecialString(“Doomed”); std::string *ps; … ps = pss; …. delete ps; // UNDEFINED Java can prevent programmers from extending a class… C++ can’t

  40. A destructor trick • Maybe you have a class that you want to be abstract, but you don’t have any pure virtual functions. • Make the destructor pure virtual • BUT – you still have to provide a definition, because the compiler always calls the base class destructor. class AWOV { public: virtual ~AWOV()=0; }; AWOV::~AWOV() {}

  41. But is it really polymorphic? • The “handy rule” for base classes really applies only to polymorphic base classes – those designed to allow manipulation of derived class objects through base class interfaces. • Not always the case – Uncopyable, for example, is designed to prevent copying. You wouldn’t do: Uncopyable *uc; uc = new HomeForSale();

  42. Item #8 Prevent exceptions from leaving destructors • Why? What if you had ten Widgets in an array and the ~Widget for the first Widget threw an exception. Then ~Widget is invoked for the next Widget in the array, and it throws an exception.

  43. Example: DBConnection class DBConnection{ public: … // params omitted for simplicity static DBConnection create(); void close(); // may throw exception }; class DBConn { //manages DBConnection public: … // destructor ensures db connection always closed ~DBConn() { db.close(); } private: };

  44. DBConnection (continued) • Allows clients to: { DBConn.dbc(DBConnection::create()); … // use object } // destructor called at end of block • Problem: if db.close() fails, exception will be thrown in destructor

  45. Options for destructor • Terminate the program (OK if program cannot continue to run after this type of error) DBConn::~DBConn() { try { db.close() } catch(…) { make log entry that call failed std::abort(); } • Swallow the exception (usually a bad idea) DBConn::~DBConn() { try { db.close() } catch(…) { make log entry that call failed }

  46. A better approach… class DBConn { //manages DBConnection public: void close() { // gives client option db.close(); closed = true; } ~DBConn() { if (!closed) { try {db.close();} // backup in case client didn’t catch ( . . ) { make log entry that call failed … // terminate or swallow } } } private: DBConnection db; bool closed; };

  47. Item #9 Never call virtual functions during construction or destruction • The calls won’t do what you expect (differs from C# and Java!)

  48. Example class Transaction { // base class public: Transaction(); virtual void logTransaction() const =0; }; Transaction::Transaction() { … logTransaction(); // final act is log transaction } class BuyTransaction : public Transaction { public: virtual void logTransaction() const; }; class SellTransaction : public Transaction { public: virtual void logTransaction() const; }; BuyTransaction b; at this point, every object is of type Transaction! calls Transaction constructor first, so when logTransaction called – NOT one in BuyTransaction derived class members not initialized yet, so can’t run derived class functions.

  49. Discussion • Would be easy to catch this one, because pure virtual so program wouldn’t link. • If function were just virtual, “wrong” version would be called

  50. Item #10 Have assignment operators return a reference to *this • Assignments can be chained: int x, y, z; x = y = z = 15; Right-associative, so this is like: x = (y = (z = 15)); Widget& operator =(const Widget& rhs) { … return *this; } Widget& operator +=(const Widget& rhs) { … return *this; }

More Related