Chez ouam

/home/jmfrouin

View on GitHub
8 February 2013

C++

by Jean-Michel Frouin

Classe

Une classe définie un objet. Une classe est semblable à une struct du C. Sans spécification, par l'opérateur de portée, toutes ses données et fonctions (que l'on appelle méthodes) sont privées. Par défaut, elles sont publiques dans une struct.

Voici la définition d'une classe CClasse:

#include <iostream>

class CClasse
{
  public:
    //Constructeur
    CClasse(int a = 3);

    //Constructeur par copie
    CClasse(CClasse& a, int b = 0);

    //Destructeur
    ~CClasse() { }

  public:
    void Methode1(int a = 10)
    {
      std::cout << "a = " << a << std::endl;
    }

    void Methode1(int a, int b = 5)
    {
      std::cout << "a = " << a << " et b = " << b << std::endl;
    }
};

int main()
{
  CClasse A;
  CClasse* B = &A;
  A.Methode1(2.0);
  A.Methode1(2.0, 2.0);
  B->Methode1(2.0, 2.0);
  return EXIT_SUCCESS;
}

Pour pouvoir accéder à une méthode d'une classe il faut utiliser soit . (ligne 31) si on manipule un objet, sinon -> (ligne 32) si on manipule un pointeur sur l'objet.

L'initialisation (L'initialisation des données membres se fait dans l'ordre de déclaration des données membres. et non dans l'ordre où on les initialise) des données membres d'une classe peut se faire de deux façons dans le constructeur:

CClasse::CClasse()
{
mA = 4;
mB = 5;
}
CClasse::CClasse():
mA(4),mB(5)
{

}

Enumération

Les énumérations permettent de créer une entité à partir d'un ensemble de valeurs numériques.

enum ECodesErreurs
{
  eOK, // = 0
  eKO, // = 1
  eNA = 18, // = 18
  eN2, // = 19
  eN3 = 69, // = 69
};

Méthodes

Définition

Toutes les fonctions d'une classe s'appellent des méthodes. Il est possible de redéfinir le comportement d'une méthode dans une classe dérivée, si dans la classe de base, la méthode à été définie avec le mot clef virtual (Quand une méthode est surchargée, le comportement de base n'est plus accessible à la classe fille, et donc il faut surcharger toutes les méthodes virtuelles). La signature (Nombre de paramètres et valeur de retour d'une méthode) doit être la même. On peut toutefois modifier le type de la valeur retour, si celui ci hérite du type de base.

Il est possible de déclarer plusieurs fois la même méthode (même nom), si il est possible (au compilateur) de les différencier (En clair, si leur signatures est différentes !). Le compilateur ne peut pas deviner ce que l'on veut faire.

C'est pourquoi il n'est pas possible de différencier deux méthodes portant le même nom par leur valeur de retour, mais on peut les différencier grâce à leur arguments.

Ainsi dans CClasse, deux méthodes Methode1 sont déclarées (et définies). Chacune de ces méthodes prises individuellement est valide, mais dans le contexte il va y avoir un soucis.

En effet lors de l'appel de Methode1(2.0) à la ligne 24, la compilateur ne saura pas si il doit utiliser Methode1(a=2) ou Methode1(a=2, b=5) et donc ne compilera pas le source. Donc il faut veillez à ne pas produire ce genre d’ambiguïté.

Un autre avantage, est que le compilateur peut caster les paramètres. Ainsi à la ligne 25, le compilateur castera 2.0 en 2. Il préviendra tout de même, grâce à un warning, du cast qu'il s'est permis de faire.

Sans modification du code, la première déclaration ne pourra jamais être atteinte car elle est ambigüe.

Le résultat de l’exécution de ce programme : a = 2 et b = 2.

Arguments

Les arguments d'une méthode peuvent avoir des valeurs par défaut.

CClasse(int a = 0);

Dans ce cas si l'on créé un objet A ainsi : CClasse A, a vaudra 0. Si on le créé ainsi : CClasse A(5), alors a vaudra 5.

Méthode abstraite

Une méthode peut être virtuelle pure:

virtual Methode() = 0;

Dans ce cas elle n'a pas de comportement défini. Son comportement devra être implémenté dans la classe qui en dérive. Les méthodes virtuelles pures sont souvent utilisées dans les interfaces. (Classe abstraite servant à la dérivation, il faut voir les interfaces comme des patrons pour écrire des classes plus complexes)

Dans une classe, deux méthodes sont assez particulières : le constructeur et le destructeur.

Constructeur

Le constructeur (qui n'est pas forcement public), ici CClasse(), est la première méthode qui sera appelée lors de la création de l'objet CClasse. Un constructeur n'a pas de valeur de retour.

Un constructeur peut prendre autant de paramètres qu'il le nécessite, mais ne doit pas avoir de paramètre de type classe (dont il fait partie), sinon cela spécialise le constructeur en constructeur par copie. Dans ce cas, touts les paramètres doivent avoir une valeur par défaut.

Un autre usage peut être fait du constructeur, il peut servir de convertisseur implicite. Par exemple CClasse(double d) sera un convertisseur implicite permettant de convertir n'importe quel double en CClasse. Pour que la conversion implicite n'est pas lieu il faut utiliser le mot clé explicit il est conseillé de déclarer tous les constructeurs possédant un seul argument (donc potentiellement convertisseur de type) avec le mot clé explicit pour éviter tout malentendu. Bien entendu, le constructeur par copie est l'exception qui confirme la règle (En fait le constructeur par copie possède un autre rôle et donc n'a pas besoin d'être déclaré explicitement).}. On l'utilise ainsi : CClasse Tmp = 20.0 (Dans ce cas le symbole =, n'est pas l'opérateur d'affectation mais bel et bien le constructeur implicite de conversion d'un double en CClasse).
Un constructeur peut lever des exceptions.
Il est possible d'avoir plusieurs constructeur par défaut dans une même classe.

Destructeur

Le destructeur est la dernière méthode qui sera appelée avant la destruction de l'objet. Il est donc important de regrouper dans le destructeur tout les mécanismes de libération de la mémoire. Un destructeur n'a pas de valeur de retour ni de paramètre. Il ne doit pas lever d'exception. Un destructeur d'une classe de base devra toujours être déclaré comme virtuel. Car si une classe est détruite en ce servant d'un pointeur sur une classe de base, et que le destructeur de cette classe de base n'est pas virtuel, alors le destructeur de la classe que l'on détruit ne sera pas appelé.

Espace de nom

Introduction

L'espace de nom permet de définir plusieurs classes, définitions, énumérations ... portant le même nom mais dont la portée est différente. Il est possible de signaler que l'on souhaite utiliser un espace de nom bien précis en utilisant using namespace nom (Il convient d'utiliser using namespace après toutes les inclusions de fichiers (pour éviter que la directive n'affecte les objets) d'en-tête).

\begin{lstlisting}[language=c++]
namespace Outils
{
class A;
}

namespace Divers
{
class A;
}

Ainsi on a définit deux classes bien distinctes mais qui porte le même nom. Pour pouvoir y accéder il faut utiliser l'opérateur de résolution de porté Outils::A et Divers::A.

Espace de nom anynome

namespace
{
}

Remarques

Il ne faut pas déclarer d'objet statique (donc avec le mot clé static) à l'intérieur d'un espace de nommage. A la place il faut déclarer ces objets dans l'espace de nom anonyme.

Héritage

On dit qu'une classe B hérite d'une classe A, si B est définie ainsi :

class B : public A
{
...
};

Là, encore, la portée est importante bien que rarement autre que public (on se sert de la portée des données membres en général).

Portée

Dans l'exemple CClasse on constate qu'une méthode ou une donnée membre peut être soit public, dans ce cas n'importe qui y a accès, soit protected, auquel cas seules la classe elle même et ces classes filles y auront accès, soit private, dans ce dernier cas seule la classe elle même y a accès.

Ainsi, A1() ne sera accessible que par A et ses filles. A2() par A uniquement. Tandis que la donnée membre m_A sera accessible par tout le monde.

Instruction de branchement

Ne jamais modifier la variable de contrôle d'une instruction de branchement sous peine de tomber dans une boucle infinie :

for(int i=0; i!=8; ++i)
{
  ++i; //A banir du code !
}

switch

Ne pas oublier le break !

switch(cond)
{
  case 0:
  case 1:
    break;
  default:
    break;
}

Classe abstraire

Une classe abstraite (Pour rendre le code plus lisible il peut être intéressant de rendre les constructeurs d'une classe abstraite privés.) est une classe qui possède au moins une méthode virtuelle pure. Donc une méthode définie ainsi : virtual void Methode() = 0;. Les classes abstraites ne peuvent pas être instanciées et ne servent que de classe de base, pour l'héritage ou le polymorphisme.

Une méthode virtuelle pure devra être implémentée par la classe fille.

Cohabitation C/C++

Convertir une std::string en char*

std::string chaine;
chaine.c_str();

Mangling des fonctions C en C++

#ifdef __cplusplus
extern C {
#endif

function();
...

#ifdef __cplusplus
}
#endif

Démangling

Un petit programme bien pratique, permet de demangler les noms des méthodes du C++ : c++filt.

Données et méthodes statiques

Il est possible de définir des données membres ou des méthodes comme static. Une donnée membre statique aura toujours la même valeur dans toutes les instances d'une classe qui l'encapsule. Les méthodes déclarées static ne pourront manipuler que des données statiques et pourront être appelées sans qu'une instance de la classe soit instanciée.

class CClasse
{
  static CClasse* mInst;
};

Toutes données membres déclarées static devront faire l'objet d'une instanciation hors de la déclaration (même mInst qui est private):

CClasse* CClasse::mInst = 0;

Modificateurs d'Entrée/Sortie

std::boolalpha : Active le format binaire.
std::dec : Active le format décimal.
std::endl : Fin de ligne.
std::ends : Ajout le caractère nul.
std::flush : Vide le tampon.
std::hex : Active le format hexadécimal.
std::left : Alignement à gauche.
std::oct : Active le format octal.
std::noboolalpha : Désactive le format binaire.
std::noshowbase : N'affiche plus le format utilisé.
std::noshowpoint : N'affiche pas la virgule.
std::right : Alignement à droite.
std::showbase : Affiche le format utilisé (hex : 0x,dec ou oct : 0).
std::showpoint : Affiche la virgule.

Exceptions

Pour pouvoir améliorer le suivi de la vie d'une application C++, il est important d'avoir un gestionnaire d'exceptions. Ce gestionnaire d'exception fourni la pile d'appel lorsqu'une exception est détectée. Il n'est pas terminé pour le moment.

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <exception>

class CException: public std::exception
{
  public:
    CException(const std::string& File, const std::string& Line)
    {
      mFile = File;
      mLine = Line;
    }
    virtual const char* what() const throw()
    {
      std::cout << "CException" << '\n';
      std::cout << "File : " << mFile << ", Line " << mLine << '\n';
      std::cout << "Call stack : " << '\n';
      displayStack();
      return "CException";
    }
  private:
    void displayStack()
    {
      void* array[10];
      sizet size;
      char **strings;
      size = backtrace(array, 10);
      strings = backtracesymbols(array, size);

      for(sizet i = 0; i < size; ++i)
      {
        std::cout << strings[i] << '\n';
      }
      free(strings);
    }
  private:
    std::string mFile;
    std::string Line;
}MyEx;

int main()
{
  try
  {
    throw MyEx(FILE, LINE);
  }
  catch(std::exception& e)
  {
    std::cout << e.what() << std::endl;
  }

  return EXITFAILURE;
}

Héritage multiple

Soit A une classe de base, comportant une méthode Affiche(). B et C, deux classes, héritent de A. Enfin D, une classe qui hérite à la fois de B et de C. D implémente donc le concept de multihéritage.

Maintenant que ce passe t'il si on instancie une classe D et que l'on essaie d'accéder à la méthode Affiche(), pour ce faire écrivons les choses en C++:

#include 

class A
{
  public:
    void Affiche()
    {
      std::cout << this << std::endl;
    }
};

class B : public A
{
};

class C : public A
{
};

class D : public C, public B
{
};

int main()
{
  D D1;
  D1.Affiche();
  return EXIT_SUCCESS;
}

Et bien, lors de la compilation, il y aura un gros problème :

heritage_multiple_1.cpp: In function 'int main()':
heritage_multiple_1.cpp:27: error: request for member 'affiche' is ambiguous
heritage_multiple_1.cpp:6: error: candidates are: void A::affiche()
heritage_multiple_1.cpp:6: error: void A::affiche()

Comme le dit le message d'erreur : c'est ambigu.
En effet, D hérite de B et de C. Donc lors de l'instanciation de D, deux classes, B et C, seront créées en mémoire, ainsi que 2 classes A (une pour B et une pour C). Du coup quand D voudra appeler Affiche() le compilateur ne saura pas si il s'agit de la méthode de la classe A instanciée par B ou de la classe A instanciée par C.

On aura, donc, le schéma suivant en mémoire :

    A
   /  \
  /    \
 /      \
B        C
 \      /
  \    /
   \  /
     D

Si c'est bien le schéma que l'on veut reproduire, il faut utiliser l'opérateur de résolution de portée pour résoudre le problème : D1.B::Affiche().

Si par contre le schéma mémoire attendu est celui-ci :

A        A
|        |
|        |
|        |
B        C
 \      /
  \    /
   \  /
     D

Alors il faudra utiliser le mot clef virtual pour décrire l'héritage de B et de C. Cela donne :

#include 

class A
{
  public:
    void Affiche()
    {
      std::cout << this << std::endl;
    }
};

class B : virtual public A
{
};

class C : virtual public A
{
};

class D : public C, public B
{
};

int main()
{
  D D1;
  D1.Affiche();
  return EXIT_SUCCESS;
}

Macros

Colorer la sortie standard ou erreur sous bash

Il suffit d'inclure dans le code :

#define VERT "\e[0;32m"
#define ROUGE "\e[0;31m"
#define BLEU "\e[0;34m"
#define VIOLET "\e[0;35m"
#define CYAN "\e[0;36m"
#define STOP "\e[0m"

Puis pour l'utiliser (STOP permet d'annuler la coloration, sans quoi les futurs std$::$cout seraient aussi colorés) :

std::cout << '\n' << VERT << "Command Line Interface " << VER << STOP << '\n';
std::cout << ROUGE << "Erreur : Pointeur == 0" << STOP << '\n';

Convertir un code d'erreur en chaine

#include

typedef enum
{
eOK = 0,
eNOK,
}Return_Code;

#define t(u) #u

int main()
{
std::cout << t(eOK) << std::endl;
return 0;
}

Définir un numéro de version complet

Pour pouvoir gérer simplement un numéro de version complet, donc composé d'un numéro de version majeure (evolution majeure), de version mineure (ajout de fonctionnalité) et d'un numéro de bug (correction des bugs découvert dans l'application). Gràce à cette ensemble de macro il est possible de maintenir facilement le numéro de version de plusieurs composants.

#define MAJOR 0
#define MINOR 1
#define BUG 1
#define t(u) #u
#define VERSION(M,m,b) t(M) "." t(m) "." t(b)
#define VER VERSION(MAJOR,MINOR,BUG)

Operateurs de cast

const_cast

Permet d'ajouter ou de supprimer : const ou volatile.

reinterpret_cast

Pointeur permettant de convertir un pointeur vers n'importe quel autre type.

dynamic_cast

L'opérateur dynamic_cast converti un pointeur sur une classe mère en un pointeur sur classe fille dans la même chaine d'héritage. De plus l'opérateur dynamic_cast ne fonctione qu'avec des objets polymorphiques (dont une des méthodes est virtuelle).

Cast d'un pointeur (APtr) sur un objet A, en un pointeur sur un objet B (B hérite de A) :

dynamic_cast<B*>(APtr);

De plus, il faut savoir que le compilateur:

Vérifie que le pointeur (ou la référence) que l'on essaie de caster avec l'opérateur dynamic_cast est bien un pointeur sur un objet polymorphique.

Si tel n'est pas le cas, ou si il échoue, il renvoie un pointeur nul ou lève une exception bad_cast.

Pour tester si une plateforme supporte le dynamic_cast on peut utiliser le code suivant :

#include 

class A
{
  public:
    virtual ~A(){}
};

class B : public A
{
  public:
    B():
    mA(9)
    {
    }
    void BB()
    {
      std::cout << "Youpi" << mA << std::endl;
    }
  public:
    int mA;
};

int main()
{
  A* A0;
  A0 = new B;
  dynamic_cast<B*>(A0)->BB();
  delete A0;
  return EXIT_SUCCESS;
}

static_cast

Fonctionne comme dynamic_cast sauf qu'il peut être utilisé avec des objets non polymorphiques. En conséquence, le compilateur n'éffectue pas les tests de contrôle.

Operateur new

Introduction

Dans la norme C++, page 47, on lit :

Even if the size of the space requested is zero, the request can fail. If the request succeeds, the value returned shall be a non null
pointer value (4.10) p0 different from any previously returned value p1, unless that value p1 was subsequently
passed to an operator delete. The effect of dereferencing a pointer returned as a request for
zero size is undefined. (The intent is to have operator new() implementable by calling malloc() or calloc(), so the rules are substantially the
same. C + + differs from C in requiring a zero request to return a non-null pointer.)

Ainsi, lorsque new ne peut pas allouer un objet il lève l'exception bad_alloc.

Surcharge

Si l'on souhaite que new renvoie NULL en cas de problème mémoire il faut surcharger l'opérateur :

struct nothrow_t {};
extern const nothrow_t nothrow;
void *operator new throw() (size_t, const nothrow_t&);

Un bon article sur l'opérateur new, contenant notamment sa surcharge pour qu'il renvoie NULL entre autre.

std::nothrow

On peut aussi désactiver les exceptions sur new :

int *adr = new(std::nothrow) int [taille];

Lors d'un échec dans l'allocation memoire il renverra, dans ce cas, un pointeur nul.

set_new_handler

On peut aussi utiliser cette instruction pour definir une methode qui sera appelée en cas d'echec d'allocation de l'espace memoire par l'operateur new.
Il nécessite l'inclusion de $<$new$>$.

void depasse();
set_new_handler(depasse);

Patron de classe

Introduction

Un patron de classe, est en fait une classe générique. Générique en ce sens, qu'elle peut posséder des données membres d'un type inconnu. Elle peut aussi retourner un type encore inconnu lors de l'écriture du patron. Un patron de classe ne possède pas de corps (pas de .cpp donc) et ce présente donc sous la forme d'un fichier de en tête (donc .h). En effet le compilateur ne peut pas compiler quelque chose qu'il ne connait pas. En fait, les types inconnus que l'on manipule seront connus au moment de la pré-compilation, et donc de la compilation. (Puisque, à ce moment là, le compilateur à conscience de l'intégralité du programme)

On peut aussi séparer la déclaration d'un template de sa définition. (mais en incluant le fichier de définition à la fin de l'en tête.). Dans ce cas, le corps portera l'extension .tpl. (Convention)

#ifndef __PATRON__
#define __PATRON__

#include "PATRON.tpl"

#endif //__PATRON__

Du coup pour l'utilisateur du patron, il suffit de faire un include de l'en tête de la classe.

Il y a quand même un premier effet de bord, dû à cette souplesse, le fait que lors de la pré-compilation le compilateur "comble" les trous ,en remplacant un type inconnu par un type connu, il le fait dans un en tête. On se retrouve bien vite avec beaucoup de code binaire en trop (ou alors beaucoup de symboles inutiles) ce qui est peut être critique pour des projets embarqués.

Patron simple

Pour écrire un patron, on utilise template:

template
class MonPatron
{
T Ajoute()
{
return mNb+1;
}
private:
T mNb;
}

Ici, on a écrit une classe MonPatron, qui possède une donnée membre de type inconnu, et une méthode qui retourne un type inconnu que l'on nomme T pour plus de simplicité.

Pointeurs intélligents

Introduction

Les smarts pointeurs, sont des objets au sens C++ qui fonctionne comme des pointeurs standards, sauf qu'ils implémentent de nouvelles fonctionnalités, comme par exemple l'éffacement de l'objet pointé.

Deux types de pointeurs intélligents peuvent être distingués, les scopes pointeurs qui sont des pointeurs qui détruisent l'objet pointé une fois sorti de la portée, et les shared pointeurs qui gardent à jour un compteur de référence sur l'objet pointé.

auto_ptr

La STL implémente une classe auto_ptr, pour pouvoir l'utiliser il faut inclure l'en-tête memory : auto_ptr$<$class T$>$ nom;

Voyons comment l'auto_ptr fonctionne :

Sans :

CClasse* tmp = new CClasse; 
tmp$\rightarrow$Methode();
delete tmp;

Avec :

#include $<$memory$>$
auto\_ptr$<$CClasse$>$ tmp(new CClasse);
tmp$\rightarrow$Methode();

Les deux codes font strictement la même chose, mais d'une manière plus "simple" avec les auto pointeurs, car le développeur n'a plus à ce soucier de la destruction des objets.

Smart pointeur utilisé comme dumb pointeur

Pour pouvoir fonctionner toutes les classes qui seront manipulées par les CSmartPtr devront dériver d'une classe CSmartCpt. La classe CSmartCpt permet juste d'assigner un compteur à un objet.

On agrège, ainsi, directement le compteur à l'objet pointé. Cela permet, entre autre, d'éviter de fragmenter la mémoire.

L'implémentation d'un smart pointeur est d'autant plus simplifiée qu'elle ne gère pas de compteur en interne :

#ifndef __SMART_POINTER__
#define __SMART_POINTER__

#include 

#if defined LEAKS
#include <leak_detector.h>
#endif

typedef unsigned int SmartCptType;

/*!
 * @brief Simple counter used by Smart pointer.
 */
class CSmartCpt
{
  public:
    CSmartCpt(): mCounter(0) {}
    virtual ~CSmartCpt() {}

    SmartCptType _incRef() { return ++mCounter; }
    SmartCptType _decRef() { return --mCounter; }

  private:
    SmartCptType mCounter;       ///< Reference counter.
};

/*!
 * @brief Pointer which can handle multireference.
 * This smart pointer erase the object only when nobody use it anymore by maintaining a reference counter.
 * Be careful : This implementation is written for SAPI project and is not yet fully implemented.
 * For the moment, this smart pointer doesn't support array object.
 */
template 
class TSmartPtr
{
  public:
    TSmartPtr(): mObject(0) {}
    ~TSmartPtr() { *this = 0; }

    TSmartPtr& operator=(T* object)
    {
      if(object) object->_incRef();

      if(mObject)
      {
        SmartCptType Cpt;
        Cpt = mObject->_decRef();
        if(Cpt == 0) delete mObject;
      }
      mObject = object;
      return *this;
    }

    TSmartPtr& operator=(TSmartPtr& object)
    {
      *this = object.mObject;
      return *this;
    }

    //In order to use it, as a dumb pointer.
    operator T*() { return mObject; }

    bool operator==(const int& value) const
    {
      return ((int)this == value);
    }

    bool operator!=(const int& value) const
    {
      return ((int)this != value);
    }

    //Dereferencement operators for object.
    T& operator*() { return *mObject; }

    //Dereferencement operators for pointer on object.
    T* operator->() { return mObject; }

  private:
    T* mObject; ///< Object handle by this pointer.
};
#endif //__SMART_POINTER__

Shared pointer

Pointent sur un objet en ce servant d'un compteur (incrémenté) à chaque fois que l'on pointe sur l'objet. Ainsi lorsque ce pointeur vaut 0 l'objet peut être effacé.

Scope pointer

Detruisent les objets sur lesquels ils pointent lors de leur destructions.

RTI : RunTime Informations

Les informations RTI, sont des informations disponible lors de la compilation et fournies par le compilateur.

On peut ainsi, récupérer le type d'un objet (ou d'un type) avec : const type_info &Type = typeid(A) en supposant que A à été déclaré précédemment.

Singleton

Introduction

Un singleton est un motif de conception pour décrire un objet qui n'est instancié qu'une fois en mémoire durant tout le temps d'éxécution du programme.
Le singleton est juste la façon de coder cet objet particulier.

Implémentation

Pour implémenter un singleton, on peut utiliser un pointeur statique sur l'objet:

#include 

template
class TSingleton
{
	protected:
		TSingleton() { }

		virtual ~TSingleton() { }

	public:
		void stop()
		{
			if(mInst)
			{
				delete mInst;
				mInst=NULL;
			}
		}

		T* instance()
		{
			T* Ret = mInst;
			if(!Ret)
				Ret = mInst = new T;
			return Ret;
		}

	private:
		static T*  mInst;
};

template
T* TSingleton::mInst = 0;

class CClasse : public TSingleton
{
    	friend class TSingleton;
};

int main()
{
	CClasse T;
	CClasse U;
	for(int i=0; i<3; ++i)
	{
		std::cout << T.instance() << std::endl;
		std::cout << U.instance() << std::endl;
	}
	U.stop();
	return EXITSUCCESS;
}

Une seconde méthode consiste à retourner une référence sur un objet statique :

#include 

template
class TSingleton
{
  protected:
    TSingleton()
    {
    }

    virtual ~TSingleton()
    {
    }

  public:
    T& instance()
    {
      static T Ret;
      return Ret;
    }
};

class CClasse : public TSingleton
{
  friend class TSingleton;
};

int main()
{
  CClasse T;
  CClasse U;
  for(int i=0; i<3; ++i)
  {
    std::cout << &(T.instance()) << std::endl;
    std::cout << &(U.instance()) << std::endl;
  }
  return EXIT_SUCCESS;
}

Surcharge operator*

Surcharge operator<<

Pour pouvoir utiliser l'opérateur de sortie, directement avec sa classe, il faut déclarer la méthode, à surcharger, comme friend de notre classe.
Ainsi dans le fichier en-tête :

#include
#include

class CString : protected std::string
{
friend std::ostream& operator<<(std::ostream& _os, const CString& _string);
}

Et sa définition sera mise dans le fichier corps de la classe :

std::ostream& operator<<(std::ostream& _os,const CString& _string)
{
const std::string* l_tmp;
l_tmp = &_string;
_os << (*l_tmp);
return _os;
}

Surcharge operator+

Pour effectuer la surcharge de l'operator+, la même méthode que pour l'operator<< peut être utilisée mais une autre méthode est plus commode.

Elle consiste à intégrée la méthode à surchargée comme une méthode classique de votre classe.

Ainsi on aura la déclaration suivante :

#ifndef _CLASSE_H_
#define _CLASSE_H_

#include
#include

class CClasse
{
public:
CClasse();
~CClasse();

const CClasse operator+(const char* _string);
const CClasse operator+(const CClasse& _classe);
}

#endif //_CLASSE_H_

On note, au passage, que rien ne nous empêche de surcharger operator+ avec différent type de paramètres.

La définition, quant à elle, est la suivante :

const CClasse CClasse::operator+(const char* _string)
{
CClasse l_ret;
//Traitement sur l_ret
return l_ret;
}

Caractères échappés

\a : Alerte (buzzer).
\b : Backspace.
\f : Saute une page.
\n : Saute une ligne.
\r : Retour chariot.
\t : Tabulation horizontale.
\v : Tabulation verticale.
\\ : Antislash.
\' : Apostrophe.
\" : Guillemets.
\? : Point interrogation.

tags: