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.

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é.

Warning: main() [function.main]: open_basedir restriction in effect. File(/mnt/145/sdb/9/8/sroccaserra/wiki/skins/common/images/icons/Images-functions.txt) is not within the allowed path(s): (/mnt/109/sdb/9/8/sroccaserra) in /mnt/109/sdb/9/8/sroccaserra/wiki/includes/OutputPage.php on line 2

Warning: main(/mnt/145/sdb/9/8/sroccaserra/wiki/skins/common/images/icons/Images-functions.txt) [function.main]: failed to open stream: Operation not permitted in /mnt/109/sdb/9/8/sroccaserra/wiki/includes/OutputPage.php on line 2

Warning: main() [function.include]: Failed opening '/mnt/145/sdb/9/8/sroccaserra/wiki/skins/common/images/icons/Images-functions.txt' for inclusion (include_path='/mnt/109/sdb/9/8/sroccaserra/include:.:/usr/php4/lib/php') in /mnt/109/sdb/9/8/sroccaserra/wiki/includes/OutputPage.php on line 2