short add(short i, short j)
{ return i+j; }
Le compilateur peut annoncer un warning, pourquoi ?
class CString
{ char* pt;
public:
CString(const char* src)
{ pt=new char[strlen(src)+1];
strcpy(pt,src);
}
~CString()
{ delete [] pt;
}
};
CString f()
{ CString str("test");
return str;
}
void main()
{ CString x=f();
}
C'est incorrect, pourquoi ?
class CString
{ char* pt;
public:
CString()
{ pt=(char*)::malloc(4);
::strcpy(pt,"ABC");
cout << "CString::CString()" << endl;
}
~CString()
{ ::free(pt);
cout << "CString::~CString()" << endl;
}
};
void main()
{ CString* pt=new CString[3];
// ...
delete pt;
}
Qu'affiche
main
, pourquoi et comment corriger ?
void main()
{
char* pt;
pt=new char [10];
// ...
delete pt;
}
C'est incorrect, pourquoi ? (N.B.: char n'est pas un objet !)
class CFigure
{ public:
virtual void trace()
{ cout << "CFigure::trace()" << endl;
}
~CFigure()
{ cout << "CFigure::~CFigure()" << endl;
}
};
class CCercle : public CFigure
{ public:
virtual void trace()
{ cout << "CCercle::trace()" << endl;
}
~CCercle()
{ cout << "CCercle::~CCercle()" << endl;
}
};
void main()
{
CFigure* p;
p=new CFigure();
p->trace();
delete p;
p=new CCercle();
p->trace();
delete p;
}
Qu'affiche
main
, pourquoi et comment corriger ?
class CHopital
{ public:
void afficheNom()
{ cout << "CHopital::afficheNom()" << endl;
}
};
void main()
{
void (*ptf)(void); // Pointeur de fonction
ptf=&CHopital::afficheNom;
ptf(); // Appel de CHopital::f()
}
Ce n'est pas compilable, pourquoi et comment corriger ?
class CHomme
{ int age;
public:
virtual void trace() const
{ cout << "CHomme::trace()" << endl;
}
virtual ~CHomme()
{}
};
class CDocteur : public CHomme
{ const char* diplome;
public:
virtual void trace() const
{ cout << "CDocteur::trace()" << endl;
}
};
void f(const CHomme& x)
{ x.trace();
}
void g(const CHomme x)
{ x.trace();
}
void main()
{ CDocteur docteur;
f(docteur);
g(docteur);
}
Qu'affiche
main
, pourquoi ?
template <class T>
class A
{ public:
bool b(const T*) { return true; }
bool b(const void*) { return false; }
};
template <class T1,class T2>
inline bool B(const T1&,const T2& t2)
{
return (A<T1>().b(&t2));
}
Que fait la fonction B ?
class CString
{ char* buf;
public:
CString(const char* str)
{ buf=(char*)::malloc(::strlen(str)+1);
::strcpy(buf,str);
}
CString(const CString& x)
{ buf=(char*)::malloc(::strlen(x.buf)+1);
::strcpy(buf,x.buf);
}
~CString()
{ ::free(buf);
}
CString& operator =(const CString& x)
{ ::free(buf);
buf=(char*)::malloc(::strlen(x.buf)+1);
::strcpy(buf,x.buf);
return *this;
}
};
Cette classe présente un risque d'erreur potentiel à l'utilisation. Dans quel cas et
comment corriger ?
template <class T> class A
{ T m;
public:
A(const T z)
{ m=z; }
};
Ce template peut échouer, pour quel type d'objet ?
class CBorne
{ int maximum;
int minimum;
public:
CBorne(int t) : minimum(t), maximum(minimum +1) {}
void print() { cout << minimum << ',' << maximum << endl; }
};
void main()
{ CBorne borne=3;
borne.print();
}
Qu'affiche
main
, pourquoi ?
class A
{ public:
int a;
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
void main()
{
cout << "offset de a dans D " << offsetof(D,a) << endl;
}
La macro offsetof de la norme Ansi C permet d'obtenir l'offset d'un attribut dans une structure. Ce
n'est pas exécutable, pourquoi ?
class CFigure
{ public:
virtual void trace()
{ cout << "CFigure::trace()" << endl;
}
// Constructeur
CFigure()
{ trace();
}
virtual ~CFigure()
{}
};
class CCercle : public CFigure
{ public:
virtual void trace()
{ cout << "CCercle::trace()" << endl;
}
};
void main()
{
CFigure figure;
CCercle cercle;
cercle.trace();
}
Qu'affiche
main
, pourquoi ?
CString& f()
{ CString a="ab";
return a;
}
CString& g()
{ CString a="/dir/";
CString b="file.ext";
return a+b;
}
void main()
{ cout << f() << endl;
cout << g() << endl; // 1
}
Qu'affiche
main
, pourquoi et comment corriger ?
void main()
{
char* pt;
pt=new char [10];
// ...
pt=(char*)realloc(pt,sizeof(*pt)*20);
// ...
}
C’est incorrect, pourquoi ?
template <class T>
class A
{ public:
void f(int x);
void f(T x);
};
Cela ne fonctionne pas dans tous les cas. Pourquoi ?
class CFichierTemp
{ FILE* st;
char nom[255];
public:
CFichierTemp()
{ ::strcpy(nom,"abc.tmp");
st=::fopen(nom,"w");
}
~CFichierTemp()
{ ::fclose(st);
::unlink(nom);
}
};
void f()
{ int a;
// ...
exit(0);
}
void main()
{ CFichierTemp tmp;
f();
}
Il y a une erreur, laquelle ? Comment corriger ?
class CFigure
{ public:
CFigure()
{ cout << "CFigure::CFigure()" << endl;
}
~CFigure()
{ cout << "CFigure::~CFigure()" << endl;
}
};
static jmp_buf env;
void f()
{ CFigure figure;
longjmp(env,1);
}
void main()
{
if (!setjmp(env))
{ f();
}
else
{ // ...
}
}
Qu’affiche
main
?
class CRefEntier
{ public:
int& ri;
CRefEntier(int& i) : ri(i) {}
CRefEntier& operator =(const CRefEntier& x)
{ if (this!=&x)
{ ri=x.ri; }
return *this;
}
};
void main()
{ int i=1;
int j=2;
CRefEntier i1(i);
CRefEntier i2(j);
i1=i2;
cout << "i=" << i << ",j=" << j << endl;
}
Qu’affiche
main
?
Fichier 1 :
class CCompteur1
{ public:
int cnt1;
CCompteur1()
{ cnt1=100;
}
void dec()
{ --cnt1;
}
};
CCompteur1 GlobalCompteur1;
Fichier 2 :
extern CCompteur1 GlobalCompteur1;
class CCompteur2
{ public:
int cnt2;
CCompteur2()
{ GlobalCompteur1.dec();
cnt2=GlobalCompteur1.cnt1;
}
};
CCompteur2 GlobalCompteur2;
void main()
{ cout << "cnt1=" << GlobalCompteur1.cnt1 << endl;
cout << "cnt2=" << GlobalCompteur2.cnt2 << endl;
}
Qu’affiche
main
et pourquoi ?
class CString
{ char buf[10];
public:
CString(const char* x)
{ strcpy(buf,x); }
operator const char* () const
{ return buf; }
};
const char* Globalpt=CString("ABC");
void main()
{ cout << Globalpt << endl;
}
Qu’affiche
main
, pourquoi ?
void main()
{
int i=5;
cout << i + 3;
cout << i & 3; // Erreur
}
Ce n'est pas compilable, pourquoi et comment corriger ?
void f(char& r)
{ r=3; }
void main()
{ long l=0;
f((char&)l);
cout << l << endl;
}
Qu'affiche
main
?
class CFigure
{ public:
virtual void trace() =0;
// Constructeur
CFigure()
{ trace();
}
virtual ~CFigure()
{}
};
class CCercle : public CFigure
{ public:
virtual void trace()
{ cout << "CCercle::trace()" << endl;
}
};
void main()
{
CCercle cercle;
cercle.trace();
}
Qu’affiche
main
et pourquoi ?
void f(long) { cout << "f(long)" << endl; }
void f(char*) { cout << "f(char*)" << endl; }
void main()
{ f(3);
f(NULL);
}
Qu'affiche
main
, pourquoi ?
class CApPhoto;
class CObjectif
{ public:
operator CApPhoto() const;
};
class CApPhoto
{ CObjectif _objectif;
public:
CApPhoto(const class CObjectif& objectif)
: _objectif(objectif) {}
};
inline CObjectif::operator CApPhoto() const
{ return CApPhoto(*this); }
void f(const CApPhoto& apphoto)
{ cout << "f" << endl;
}
void main()
{ CObjectif objectif;
f(objectif); // Erreur
}
Ce n’est pas compilable, pourquoi ?
class CObjNom
{ public:
void* operator new(size_t,const char*);
};
void main()
{ CObjNom* p;
p=new ("main") CObjNom();
p=new CObjNom(); // Erreur
}
Ce n’est pas compilable, pourquoi ?
class CCompte;
class CFenetreCompte;
class CClient
{ CCompte* _compte;
public:
CClient(CCompte* compte) : _compte(compte)
{ }
void affiche();
};
inline CClient* createClient(CFenetreCompte* fenCpt)
{ return new CClient((CCompte*)fenCpt); } //1
class CFenetre
{ int _taille;
//...
};
class CCompte
{ int _solde;
public:
virtual int solde() const
{ cout << "CCompte::solde" << endl;
return _solde;
}
virtual ~CCompte()
{}
};
class CFenetreCompte : public CFenetre,public CCompte
{
};
void CClient::affiche()
{ _compte->solde();
}
void main()
{ CFenetreCompte c;
CClient* client=createClient(&c);
client->affiche();
}
Ce n’est pas exécutable, pourquoi ?
class CEntier
{ int _n;
public:
CEntier()
{ cout << "CEntier::CEntier()" << endl;
}
CEntier(int n)
{ _n=n;
cout << "CEntier::CEntier(int)" << endl;
}
void operator=(int n)
{ _n=n;
cout << "CEntier::operator=(int)" << endl;
}
};
class CDeuxEntier
{ CEntier _a;
int _b;
public:
CDeuxEntier()
{ _a=4;
_b=5;
}
};
void main()
{ CDeuxEntier c;
}
Qu’affiche
main
, pourquoi ?
class CEntier
{ int i;
public:
CEntier(int z=0)
{ i=z; }
int operator ++()
{ i++;
return i;
}
};
void main()
{ CEntier a;
a++; // Erreur
}
Ce n'est pas compilable, pourquoi ?
class CObj
{ public:
CObj()
{ cout << "CObj::CObj()" << endl; }
CObj(const CObj&)
{ cout << "CObj::CObj(const CObj&)" << endl; }
};
CObj f() { return CObj(); }
CObj g()
{ CObj rc=f();
return rc;
}
L'appel d'un constructeur de copie peut être évité, comment ?
class CEntier
{ int a;
public:
CEntier()
{ cout << "CEntier::CEntier()" << endl; }
CEntier& operator =(const CEntier&)
{ cout << "CEntier::operator =()" << endl;
return *this;
}
CEntier& operator =(int x)
{ a=x;
return *this;
}
};
void main()
{ CEntier a1;
a1=1;
// ...
CEntier a2;
a2=a1;
}
Qu'affiche
main
, pourquoi ?
class xMsg
{ public:
xMsg() {}
xMsg(const xMsg&)
{ cout << "xMsg::xMsg(const xMsg&)" << endl; }
virtual void print() const
{ cout << "print" << endl; }
virtual ~xMsg()
{}
};
void f()
{ throw xMsg();
}
void main()
{
try
{ f();
}
catch(xMsg x)
{ x.print();
}
}
Qu’affiche
main
, pourquoi ?
class CComplex
{ protected:
float reel;
float imma;
public:
CComplex();
CComplex(float xreel,float ximma);
CComplex(const CComplex& x);
// ...
CComplex& operator =(const CComplex& x);
CComplex operator +(const CComplex& x);
CComplex& operator +=(const CComplex& x);
};
CComplex CComplex::operator +(const CComplex& x)
{ return CComplex(reel+x.reel,imma+x.imma);
}
CComplex& CComplex::operator +=(const CComplex& x)
{ *this=*this+x;
return *this;
}
Les opérateurs
+
et
+=
ne sont pas optimisés, comment les
améliorer ?
class CVide
{
};
void main()
{ cout << sizeof(CVide) << endl;
cout << sizeof('A') << endl;
}
Qu’affiche
main
?
void main()
{
for (int i=0;i<10;++i)
;
if (i==10) cout << "Fin de boucle" << endl;
}
Ce n'est pas compilable, pourquoi ?
class CEntier
{ public:
int i;
CEntier(int z)
: i(z) {}
};
class CRefEntier
{ CEntier& ra;
public:
CRefEntier(CEntier& x)
: ra(x)
{ cout << "i=" << ra.i << endl;
}
};
class CEntierRefEntier : public CRefEntier
{ CEntier a;
public:
CEntierRefEntier()
: a(3), CRefEntier(a)
{}
};
void main()
{ CEntierRefEntier c;
}
Qu'affiche
main
, pourquoi ?
class CEntier
{ int a;
public:
CEntier(int x) { a=x; }
int valeur() { return a; }
};
void fn(const CEntier& a)
{ cout << a.valeur() << endl; // Erreur
}
void main()
{ CEntier a=3;
fn(a);
}
Ce programme ne se compile pas, pourquoi ?
class CEntier
{ protected:
int x;
};
class CEntierEtendu : public CEntier
{
void f()
{ CEntier* p=this;
x=5; // Ok
p->x=3; // Erreur
}
};
L'accès à
x
via
p
dans la fonction
CEntierEtendu::f()
est impossible. Pourquoi et comment corriger ?
class CFigure
{ int a;
};
class CCercle : public CFigure
{ int b;
};
class CCarre : private CFigure
{ int c;
};
void main()
{ CCercle cercle;
CCarre carre;
CFigure* pFigure;
pFigure=&cercle;
pFigure=&carre; // Erreur
}
Ce n’est pas compilable, pourquoi ? Comment corriger ?
struct A { int mya; };
struct B : A { int myb; };
struct C : virtual A { int myc; };
struct D { int myd; };
struct E : D, A { int mye; };
void main()
{
B b;
C c;
E e;
A* ptA;
ptA=&b;
cout << ((void*)ptA==(void*)&b) << endl;
ptA=&c;
cout << ((void*)ptA==(void*)&c) << endl;
ptA=&e;
cout << ((void*)ptA==(void*)&e) << endl;
}
Qu'affiche main ?
class CList
{ const CList& next;
public:
CList(const CList& x)
: next(x) {}
};
Comment créer uniquement la première instance de CList ?
class CEntier
{ public:
CEntier(int) {}
};
void f(CEntier& x)
{ // ...
}
void main()
{ f(3); // Erreur
}
Ce n’est pas compilable, pourquoi ?
class CStatic
{ public:
CStatic() { cout << "CStatic::CStatic()" << endl; }
};
void f()
{ cout << "f()" << endl;
static CStatic StaticA;
}
void main()
{ cout << "main()" << endl;
f();
f();
}
Qu’affiche main, pourquoi ?
int z;
int& a=z;
int& const b=z;
Une référence ne peut être initialisée qu’à la
construction. Elle se comporte donc comme une constante. L’opérateur égal
d’une référence, modifie l’objet référencé, mais pas
la référence elle-même. b est une référence constante et non une
référence sur une constante. Quelle est la différence entre a et b ?
class CUnEntier
{ public:
int a;
CUnEntier() : a(0) { }
};
class CDeuxEntiers : public CUnEntier
{ public:
int b;
CDeuxEntiers() : b(1) { }
};
void f(CUnEntier* tab1)
{ tab1[1].a=2;
}
void main()
{ CDeuxEntiers tab2[2];
f(tab2);
cout << tab2[0].a << endl;
cout << tab2[0].b << endl;
cout << tab2[1].a << endl;
cout << tab2[1].b << endl;
}
Qu’affiche main, pourquoi ?
class CEntier
{ int i;
public:
CEntier() : i(0) {}
void print(ofstream& o)
{ o << "CEntier=" << i; }
};
Cette écriture limite l’utilisation de la classe. Pourquoi ?
L’objectif du problème suivant est de construire une classe simulant un pointeur qui
verrouille et libère la mémoire utilisée via un Handle. Les OS comme Windows
ou Mac OS possèdent une notion de Handle mémoire. La mémoire n’est plus
référencée par un pointeur, mais via un Handle. Lorsque le programme
désire utiliser la mémoire référencée, il faut auparavant
bloquer cette mémoire pour obtenir un pointeur valide. Lorsque la zone mémoire
n’est plus nécessaire, il faut débloquer cette mémoire. Cela permet de
réunir les zones vides du tas afin de mieux utiliser la mémoire. Deux appels
successifs de
lock
pour le même handle ne renvoient pas le même pointeur
mémoire mais toujours le même contenu. Cette approche peut également être
utilisée pour augmenter artificiellement la mémoire disponible en utilisant un
fichier temporaire. Lorsque le programme appelle un
lock
, le tampon correspondant est
chargé depuis le fichier vers la mémoire. Lors d’un
unlock
, le
tampon est recopié dans le fichier. L’inconvénient de ce type d’approche
est que le programme doit scrupuleusement suivre les
locks
et les
unlocks
afin de ne pas en oublier en chemin. La classe indiquée dans l’exemple suivant permet
de s’affranchir de cela. La puissance du C++ permet de simuler un pointeur et de bloquer la
mémoire utilisable lorsque cela est nécessaire.
// Gros objet à mettre dans un fichier temporaire ou ailleurs
class CBigObjet
{ public:
char buf[10000];
CBigObjet(char xFill) { ::memset(buf,xFill,sizeof(buf)); }
};
// Fonctions ::GetHandle, ::Lock et ::Unlock
typedef int HANDLE;
// ...
// Classe de simulation de pointeur sur CBigObjet
class CPtBigObjet
{ static HANDLE lockHandle; // Dernier handle bloqué
HANDLE handle;
CBigObjet* lock() const
{ ::Unlock(lockHandle);
return (CBigObjet*) ::Lock(lockHandle=handle);
}
public:
CPtBigObjet(HANDLE h) : handle(h) { }
CPtBigObjet(const CPtBigObjet& h) : handle(h.handle) { }
CBigObjet& operator *() const { return *lock(); }
CBigObjet& operator [](int i) const { return lock()[i]; }
CBigObjet* operator ->() const { return lock(); }
// ...
};
HANDLE CPtBigObjet::lockHandle=NULLHandle;
void main()
{ // Initialise via un handle
CPtBigObjet pta=::GetHandle(sizeof(CBigObjet));
CPtBigObjet ptb=::GetHandle(sizeof(CBigObjet));
*pta=CBigObjet('A');
ptb[0]=CBigObjet('B');
pta->buf[1]='C';
pta->buf[0]=ptb->buf[0];
}
Les syntaxes d'utilisations du pointeur dans
main
montrent que l’utilisation de
l’objet est similaire à un pointeur. Pourtant, il y une erreur. Laquelle et comment la
corriger ?
Il est courant, dans une méthode virtuelle, de rappeler la méthode de la classe de
base avant d’ajouter un comportement.
class A
{ public:
virtual void f();
virtual ~A()
{}
};
class B : public A
{ public:
virtual void f();
};
void B::f()
{ A::f(); // Appel de la classe de base
// ...
}
Il serait parfois confortable de pouvoir appeler la méthode de la classe de base et de
l’indiquer par un mot clef (du type super). Comment faire ?
Il est très fréquent de vouloir construire une copie d'un objet. Le constructeur de
copie est là pour cela. Comment obtenir une copie d'un objet dont on ne connaît que la
classe de base ? Pour le modèle suivant :
class CVehicule {};
class CVoiture : public CVehicule {};
class CAvion : public CVehicule {};
Si vous manipulez un pointeur ou une référence de type CVehicule et que vous
désirez une copie de l'objet référencé, vous ne savez pas si vous devez
appeler le constructeur de copie de CVoiture ou de CAvion.
void duplique(const CVehicule& vehicule)
{ CVehicule* dup=new CVoiture((const CVoiture&)vehicule); // ???
ou new CAvion((const CAvion&)vehicule); // ???
//...
delete dup;
}
Comment faire ?
Comment interdire un objet d'être présent dans le tas ?
Comment obliger un objet à être présent dans le tas ?
Il est parfois interdit d’avoir plusieurs instances d’une classe. Une classe
n’ayant qu’une seule instance s’appelle un « singleton ». Comment
interdire la création de plusieurs instances ?
Comment rédiger le prototype d'une fonction qui peut recevoir un pointeur sur
elle-même ? Par exemple, comment écrire le prototype de la fonction
f
qui
accepte une écriture comme ceci.
f(f); // Recoit un pointeur de fonction
Il est parfois nécessaire d’avoir un objet mutant. Celui-ci est d’un type
particulier à un moment de son existence, puis il se transforme en un autre type par la
suite. Cet objet se transforme en un autre objet.
Comment proposer cela avec le C++ ?
Le C++ utilise la taille de chaque objet pour l’allocation. Pour cela, il faut que le
compilateur connaisse entièrement la classe. Il doit connaître les méthodes
appartenant à une classe et les données private ou public pour pouvoir
réserver la place nécessaire à l’utilisation de celle-ci.
Une modification mineure de la classe peut entraîner une cascade de recompilation, car
cette modification peut avoir des conséquences sur un ensemble de classes. Comment
éviter ces recompilation en cascade ?
Les anciens développeurs C préfèrent les pointeurs aux
références car ils les maîtrisent déjà. Les
références ont été ajoutées au langage pour pouvoir surcharger
l’opérateur crochet. C’était le seul moyen de pouvoir simuler un tableau
dans un objet. Cette notion n’est pas nouvelle, le langage Pascal par exemple, utilise
abondamment cette notion. Un paramètre peut être passé par valeur, ou par
référence. Cela permet d’éviter d’utiliser la notion de pointeur
qui n’est pas évidente à maîtriser rapidement.
Quand et pourquoi utiliser une référence ?
Les flux offerts par le C++ sont un service dont on peut se passer. Les programmeurs C utilisent
printf et connaissent parfaitement son utilisation. Pourquoi changer ses habitudes lorsque cela
fonctionne parfaitement ?
Comment gérer les erreurs dans les constructeurs ? Un constructeur ne renvoie pas de valeur.
Il ne peut donc pas signaler qu’il a échoué. Plusieurs approches sont
possibles.
Pour pouvoir écrire un template avec une méthode retournant la valeur d’erreur
d’un objet, il faut connaître son type. Une instance en erreur n’a pas forcement
la même taille qu’une instance sans erreur. Par exemple, la valeur
EOF
qui
indique un caractère incorrect, est stockée dans un entier et non dans un char. En
effet, toutes les valeurs possibles d’un char sont valides. Il a fallu trouver une valeur
supplémentaire à mettre dans un type de taille supérieure à char. La
fonction
fgetc()
retourne un entier et non un char pour pouvoir vérifier la
valeur EOF. Un template voulant retourner une valeur d’erreur, doit connaître les deux
types possibles à manipuler : le type de base, et le type permettant de retourner une
erreur. Comment déclarer un template du type « pile » dont la méthode
pop()
retourne l’objet paramètre, ou une valeur d’erreur lorsque la
pile est vide ? Une pile de caractère retournera le caractère présent dans
celle-ci ou
EOF
si la pile est vide. Une pile d'autres objets doit pouvoir retourner
également un code d'erreur.