C++‎ > ‎Le langage‎ > ‎

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