Questions pièges sur le C++
Question 1
short add(short i, short j)
{ return i+j; }
Le compilateur peut annoncer un warning, pourquoi ?
Question 2
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 ?
Question 3
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 ?
Question 4
void main()
{
char* pt;
pt=new char [10];
// ...
delete pt;
}
C'est incorrect, pourquoi ? (N.B.: char n'est pas un objet !)
Question 5
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 ?
Question 6
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 ?
Question 7
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 ?
Question 8
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 ?
Question 9
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 ?
Question 10
template <class T> class A
{ T m;
public:
A(const T z)
{ m=z; }
};
Ce template peut échouer, pour quel type d'objet ?
Question 11
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 ?
Question 12
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 ?
Question 13
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 ?
Question 14
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 ?
Question 15
void main()
{
char* pt;
pt=new char [10];
// ...
pt=(char*)realloc(pt,sizeof(*pt)*20);
// ...
}
C’est incorrect, pourquoi ?
Question 16
template <class T>
class A
{ public:
void f(int x);
void f(T x);
};
Cela ne fonctionne pas dans tous les cas. Pourquoi ?
Question 17
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 ?
Question 18
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 ?
Question 19
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 ?
Question 20
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 ?
Question 21
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 ?
Question 22
void main()
{
int i=5;
cout << i + 3;
cout << i & 3; // Erreur
}
Ce n'est pas compilable, pourquoi et comment corriger ?
Question 23
void f(char& r)
{ r=3; }
void main()
{ long l=0;
f((char&)l);
cout << l << endl;
}
Qu'affiche main ?
Question 24
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 ?
Question 25
void f(long) { cout << "f(long)" << endl; }
void f(char*) { cout << "f(char*)" << endl; }
void main()
{ f(3);
f(NULL);
}
Qu'affiche main, pourquoi ?
Question 26
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 ?
Question 27
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 ?
Question 28
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 ?
Question 29
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 ?
Question 30
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 ?
Question 31
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 ?
Question 32
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 ?
Question 33
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 ?
Question 34
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 ?
Question 35
class CVide
{
};
void main()
{ cout << sizeof(CVide) << endl;
cout << sizeof('A') << endl;
}
Qu’affiche main ?
Question 36
void main()
{
for (int i=0;i<10;++i)
;
if (i==10) cout << "Fin de boucle" << endl;
}
Ce n'est pas compilable, pourquoi ?
Question 37
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 ?
Question 38
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 ?
Question 39
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 ?
Question 40
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 ?
Question 41
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 ?
Question 42
class CList
{ const CList& next;
public:
CList(const CList& x)
: next(x) {}
};
Comment créer uniquement la première instance de CList ?
Question 43
class CEntier
{ public:
CEntier(int) {}
};
void f(CEntier& x)
{ // ...
}
void main()
{ f(3); // Erreur
}
Ce n’est pas compilable, pourquoi ?
Question 44
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 ?
Question 45
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 ?
Question 46
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 ?
Question 47
class CEntier
{ int i;
public:
CEntier() : i(0) {}
void print(ofstream& o)
{ o << "CEntier=" << i; }
};
Cette écriture limite l’utilisation de la classe. Pourquoi ?
Question 48
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 ?
Question 49
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 ?
Question 50
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 ?
Question 51
Comment interdire un objet d'être présent dans le tas ?
Question 52
Comment obliger un objet à être présent dans le tas ?
Question 53
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 ?
Question 54
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
Question 55
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++ ?
Question 56
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 ?
Question 57
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 ?
Question 58
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 ?
Question 59
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.
Question 60
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.