Cpp
Un article de MonWiki.
En construction...
Brefs mémos de programmation C++. Voir les références pour la bibliographie.
- Voir aussi : Conception Logicielle
Sommaire |
Principes généraux
$$$$ Move Me
- "Keep It Simple, Stupid." (KISS)
- "You Ain't Gonna Need It." (YAGNI) [1]
- "Don't worry about the future until you're sure you can survive the present." (C++ FAQs, FAQ 3.05)
- "When in doubt, do as the ints do." (Scott Meyers [2])
- "Write what you know, and know what you write." (Herb Sutter [3])
- Utiliser swap().
std::swap(v0_.y, v1_.y);
Classes
See: C++ Coding Standards.
Value classes
Value classes (e.g. std::pair
, std::vector
) are modeled after builtin types.
- public constructor, copy ctor, operator=
- no virtual function (including destructor)
- used as a concrete class (not a base class)
- instantiated on the stack or directly held as a member of another class
Base classes
- destructor is either public & virtual or protected & nonvirtual
- nonpublic copy constructor & operator=
- establishes interfaces through virtual functions
- usually instantiated dynamically on the heap and used via a (smart) pointer
Traits classes
Loosely, traits classes are templates that carry information about types.
- contains only typedefs and static functions
- no modifiable state or virtuals
- usually not instantiated
Policy classes
Policy classes are fragments of pluggable behaviour.
- may or may not have state or virtual functions
- is not usually instanciated standalone, but only as a base or member
Mémoire
- Utiliser au maximum les variables sur la pile et les auto-pointeurs.
- Pour les collections de pointeurs, utiliser le shared_ptr<> de Boost avec les containers STL.
-
const *
means constant data,* const
means constant pointer.
Les auto_ptr
La classe auto_ptr<> (cf. More Effective C++, Item 9) fait partie de la librairie standard C++ (définit dans <memory>). Exemple d'implémentation simpliste :
template<class T> class auto_ptr { public: auto_ptr(T *p = 0): ptr(p) {} // save ptr to object ~auto_ptr() { delete ptr; } // delete ptr to object private: T *ptr; };
- Say good-bye to pointers that are used to manipulate local resources.
- Use auto_ptr objects instead of raw pointers, and you won't have to worry about heap objects not being deleted, not even when exceptions are thrown.
- Exemple d'utilisation (utilise un constructeur virtuel):
class CBase { public: virtual void process() = 0; ... }; class CDerivedA: public CBase { public: virtual void process(); ... }; class CDerivedB: public CBase { public: virtual void process(); ... }; CBase * readObject(istream& is);
void processData(istream& dataSource) { while (dataSource) { auto_ptr<CBase> pCDerived(readObject(dataSource)); pCDerived->process(); } }
- Because the auto_ptr uses the single-object form of
delete
, auto_ptr is not suitable for use with pointers to arrays of objects. In such case, however, it's often a better design decision to use a vector instead of an array, anyway.
Constructeurs et destructeurs
Destructeurs
- The most derived class's destructor is called first, then the destructor of each base class is called.
- Make destructor virtual in base classes (classes that contain virtual functions).
Constructeurs
- Declare an assignment operator and a copy constructor in classes with dynamically assigned data.
- C++ will write an assignment operator & copy constructor for you if you don't declare one yourself. Except if you declare them (private, most likely) and omit to define them.
- Call Base(rhs) in Derived's copy constructor's init list. (See also assignment operator)
Constructeur Virtuel (Fonction Fabrique)
A virtual constructor (cf. More Effective C++, Item 25) is a function that creates different types of objects depending on the input it is given. Virtual constructors are useful in many contexts, only one of which is reading object information from disk (or off a network connection, etc.).
Exemple simple :
class NLComponent { // abstract base class for NewsLetter components public: ... // contains at least one pure virtual function }; class TextBlock: public NLComponent { public: ... // contains no pure virtual function }; class Graphic: public NLComponent { public: ... // contains no pure virtual function }; class NewsLetter { public: NewsLetter(istream& is); private: list<NLComponent*> components; static NLComponent * readComponent(istream& is); };
NewsLetter::NewsLetter(istream& is)
{
while (is) {
components.push_back(readComponent(is));
}
}
La fonction readComponent agit comme un constructeur virtuel.
Note : une telle fonction est aussi appelée fonction fabrique (factory function).
Opérateurs
Constructeur de copie et operator=()
"For example, have a common private member function that does the work and is called by both copy construction and copy assignment." (Herb Sutter [4])
Assignment operator
- Have operator=() return a reference to *this.
- Call Base::operator=(rhs); in Derived's operator=().
- Check for assignment to self in operator=().
if (this == &rhs) return *this;
Préfixe & postfixe
class CExemple { public: CExemple& operator++(); // prefix ++ const CExemple operator++(int); // postfix ++ CExemple& operator--(); // prefix -- const CExemple operator--(int); // postfix -- CExemple& operator+=(int); // a += operator for CExemples and ints ... };
// prefix form: increment and fetch CExemple& CExemple::operator++() { *this += 1; // increment return *this; // fetch }
// postfix form: fetch and increment const CExemple CExemple::operator++(int) { CExemple oldValue = *this; // fetch ++(*this); // increment return oldValue; // return what was fetched }
- Implémenter l'opérateur postfixe en utilisant l'opérateur préfixe (évite la duplication et garantit la cohérence)
- L'opérateur postfixe renvoie une copie const. Evite notemment i++++, qui n'aurait pas le résultat escompté.
- When dealing with user-defined types, prefix increment should be used whenever possible, because it's inherently more efficient.
STL
STL algorithms vs. hand-written loops [5]
Eg. for or for_each:
class Widget { public: ... void redraw() const; ... }; list<Widget> lw; ...
for (list<Widget>::iterator i = lw.begin(); i != lw.end(); ++i) {
i->redraw();
}
for_each(lw.begin(), lw.end(), mem_fun_ref(&Widget::redraw));
Efficiency
- Small advantage for algorithms: only one call to the container's end() function.
- Big advantage for algorithms: STL implementers can take advantage of their knowledge of container implementations to optimize traversals in a way that no library user ever could.
- The second major efficiency argument is that all but the most trivial STL algorithms use computer science algorithms that are more sophisticated — sometimes much more sophisticated — than anything the average C++ programmer will be able to come up with. It’s next to impossible to beat sort or its kin (e.g., stable_sort, nth_element, etc.); the search algorithms for sorted ranges (e.g., binary_search, lower_bound, etc.) are equally good; and even such mundane tasks as eliminating objects from vectors, deques, and arrays are more efficiently accomplished using the erase-remove idiom than the loops most programmers come up with.
Correctness
One of the trickier things about writing your own loops is making sure you use only iterators that (a) are valid and (b) point where you want them to.
Insertion in a container inside a loop invalidates all iterators, so you have to be extra carefull.
E.g. the correct way to insert elements from an array at the begining of a deque and add 41 to them:
deque<double>::iterator insertLocation = d.begin(); // update insertLocation each time // insert is called to keep the iterator valid, // then increment it for (size_t i = 0; i < numDoubles; ++i) { insertLocation = d.insert(insertLocation, data[i] + 41); ++insertLocation; }
Same operation using algorithms:
// copy all elements from data to the // front of d, adding 41 to each transform(data, data + numDoubles, inserter(d, d.begin()), bind2nd(plus<int>(), 41));
Algorithms have good behaviour builtin, you don't have to worry about iterator validity.
Maintainability
Algorithm functions have names that states their task. Any time we can replace low-level words like for, while, and do with higher-level terms like insert, find, and for_each, we raise the level of abstraction in our software and thereby make it easier to write, document, enhance, and maintain.
However sometimes it's not the case. A loop like:
// iterate from v.begin() until an // appropriate value is found or // v.end() is reached vector<int>::iterator i = v.begin(); for( ; i != v.end(); ++i) { if (*i > x && *i < y) break; } // i now points to the value // or is the same as v.end()
is more readable than its find_if counterpart.
template<typename T> class BetweenValues: public std::unary_function<T, bool> { public: // have the ctor save the // values to be between BetweenValues(const T& lowValue, const T& highValue) : lowVal(lowValue), highVal(highValue) {} // return whether val is // between the saved values bool operator()(const T& val) const { return val > lowVal && val < highVal; } private: T lowVal; T highVal; }; ... vector<int> iterator i = find_if(v.begin(), v.end(), BetweenValues<int>(x, y));
Boost
boost::bind
- Utiliser un ScopeGuard Loki avec boost::bind :
{ ... using Loki::ScopeGuard; using Loki::MakeGuard; // The search path will be removed at block exit in any case (exception safe) LOKI_ON_BLOCK_EXIT( boost::bind( &TextureHandler::RemovePath , boost::ref( TextureHandler::Instance() ) , sourcePath.branch_path() ) ); // Now, add the search path TextureHandler::Instance().AddPath( sourcePath.branch_path() ); // Some exception-prone code here ... } // The added search path is automatically removed here, even in case of an exception
Design Patterns
Factory
See: #Constructeur Virtuel (Fonction Fabrique)
Decorator
Exemple d'implémentation du motif de conception décorateur :
using boost::filesystem::path; //////////////////////////////////////////// // Classe de base abstraite (l'interface) class TextureProperties { public: // Dtor virtual ~TextureProperties(); //... virtual const path& GetPath() const = 0; //... }; ////////////////////////////////////////////////// // Dérivée, implémentation classFileTextureProperties : public TextureProperties { public: // Ctor/Dtor FileTextureProperties (); virtual ~FileTextureProperties (); //... virtual const path& GetPath() { return filePath_; } //... private: path filePath_; }; //////////////////////// // Décorateur, contient et enveloppe un composant décoré class TexturePropertiesDecorator : public TextureProperties { public: // Ctor/Dtor TexturePropertiesDecorator( const TextureProperties& tp ); virtual ~TexturePropertiesDecorator(); //... virtual const path& GetPath() { return decorated_->GetPath(); } //... protected: const TextureProperties& Decorated() const { return decorated_; } private: const TextureProperties* decorated_; };
Voir :
Traits classes
A traits class is a class template which contains only typedefs and static functions, and has no modifiable state or virtuals.
See: C++ Coding Standards, http://erdani.org/publications/traits.html, http://www.ddj.com/dept/cpp/188700800?pgno=4.
Policy classes
Policy-based class design fosters assembling a class with complex behaviour out of many little classes (called policies), each of which takes care of one behavioural or structural aspect.
See: C++ Coding Standards, Modern C++ Design.
Implementing Policy Classes
Policies implementations:
template <class T> struct OpNewCreator { static T* Create() { return new T; } }; template <class T> struct MallocCreator { static T* Create() { void* buf = std::malloc(sizeof(T)); if (!buf) return 0; return new(buf) T; } }; template <class T> struct PrototypeCreator { PrototypeCreator(T* pObj = 0) :prototype_(pObj) {} T* Create() { return prototype_ ? prototype_->Clone() : 0; } T* GetPrototype() { return prototype_;} void SetPrototype(T* pObj) { prototype_ = pObj;} private: T* prototype_; };
Using policies :
- Uses template template parameters.
-
WidgetManager
inherits its policy class.
template <template <class> class CreationPolicy> class WidgetManager : public CreationPolicy<Widget> { ... }; typedef WidgetManager<OpNewCreator> MyWidgetMgr;
Default policies
template <template <class> class CreationPolicy = OpNewCreator> class WidgetManager : public CreationPolicy<Widget> { ... };
Enriched policies
The Creator
policy prescribes only one member function, Create
. However, PrototypeCreator
defines two more functions: GetPrototype
and SetPrototype
.
Users can exploit the enriched interface :
typedef WidgetManager<PrototypeCreator> MyWidgetManager; ... Widget* prototype = ...; MyWidgetManager mgr; mgr.SetPrototype(prototype); ... use mgr ...
If the user later decides to use a creation policy that does not support prototypes, the compiler pinpoints the spots where the prototype-specific interface was used.
Destructor
Many policies don't have any data members, but rather are purely behavioral by nature. The first virtual function added incurs some size overhead for the objects of that class, so the virtual destructor should be avoided.
A solution is to have the host class use protected or private inheritance when deriving from the policy class. Howeveer, this would disable enriched policies as well.
The lightweight, effective solution that policies should use is to define a nonvirtual protected destructor:
template <class T> struct OpNewCreator { static T* Create() { return new T; } protected: ~OpNewCreator() {} };
Combining Policy Classes
template < class T, template <class> class CheckingPolicy, template <class> class ThreadingModel > class SmartPtr;
By designing SmartPtr
this way, you allow the user to configure SmartPtr
with a simple typedef
:
typedef SmartPtr<Widget, NoChecking, SingleThreaded> WidgetPtr;
template <class> struct NoChecking { static void Check(T*) {} }; template <class> struct EnforceNotNull { class NullPointerException : public std::exception {...}; static void Check(T* ptr) { if (!ptr) throw NullPointerException(); } }; template <class> struct EnsureNotNull { static void Check(T*& ptr) { if (!ptr) ptr = GetDefaultValue(); } };
template < class T, template <class> class CheckingPolicy, template <class> class ThreadingModel > class SmartPtr : public CheckingPolicy<T>, public ThreadingModel<SmartPtr> { ... T* operator->() { typename ThreadingModel<SmartPtr>::Lock guard(*this); CheckingPolicy<T>::Check(pointee_); return pointee_; } private: T* pointee_; };
Outils
Garbage collector, leak detector: http://www.hpl.hp.com/personal/Hans_Boehm/gc/leak.html
Références
Sur le web
Le site de Boost, constitué de librairies C++ indispensables (cf. boost::shared_ptr<>, boost::bind()...).
Le site de la lib Loki, contenant des implémentations pratiques a base de policies (ScopeGuard, Singleton...)
Le site d'Herb Sutter, mine d'informations et de bonnes pratiques.
Le site de C++ FAQs (cf. lectures ci-dessous).
La page de recrutement de Housemarque, avec de bonnes pistes.
Sur l'ordi de Ludo.
A lire
- STROUSTRUP (Bjarne) - The C++ Programming Language, 3e éd. Addison-Wesley, Etats-Unis, 1997, 1019 p.
- La référence...
- SUTTER (Herb) ALEXANDRESCU (Andrei) - C++ Coding Standards. Addison-Wesley, coll. "C++ In-Depth Series", Etats-Unis, 2005, 220 p.
- Pratiques efficaces et logiques de programmation C++ actuelle. Met au rebut toutes les pratiques obsolètes, héritées du C ou des premiers temps du C++.
- MEYERS (Scott) - Effective C++, 2e éd. Addison-Wesley, coll. "Professional Computing Series", Etats-Unis, 1998, 256 p.
- Drôle et passionnant, indispensable pour explorer à moindre risque les recoins obscures du C++. Le premier opus s'intéresse à la gestion mémoire, aux constructeurs-destructeurs-assignation, à l'héritage et à la conception orientée objet.
- MEYERS (Scott) - More Effective C++. Addison-Wesley, coll. "Professional Computing Series", Etats-Unis, 1996, 318 p.
- Idem. Répond aux questions sur les opérateurs, les exceptions, quelques motifs (patterns), et autres bonnes pratiques efficaces.
- MEYERS (Scott) - Effective STL. Addison-Wesley, coll. "Professional Computing Series", Etats-Unis, 2001, 259 p.
- Idem. Le plus récent des trois s'attarde sur les détails de la STL (ou plus précisément, "the parts of C++'s Standard Library that works with iterators").
- ALEXANDRESCU (Andrei) - Modern C++ Design. Addison-Wesley, coll. "C++ In-Depth Series", Etats-Unis, 2001, 323p.
- Implémentation de design patterns à l'aide de policies, lecture indispensable.
- CLINE (M.) LOMOW (G.) GIROU (M.) - C++ FAQs, 2e éd. Addison-Wesley, Etats-Unis, 1999, 587 p.
- FAQ pour répondre aux questions (de base ou complexes, de conception ou de programmation) qu'on se pose depuis x temps. Un peu daté.