Comment réussir un entretien C++ : les meilleures questions de niveau avancé
Un entretien pour un poste de développeur C++ est toujours un défi. Si vous connaissez probablement les questions de base (différence entre new et malloc, qu'est-ce qu'un destructeur virtuel), le niveau avancé exige une compréhension approfondie du fonctionnement interne du langage. Dans cet article, nous analyserons les sujets clés qui reviendront inévitablement lors d'un entretien technique pour les postes Middle et Senior. Nous ne nous contenterons pas de lister les questions, mais nous fournirons des réponses détaillées avec des exemples de code.
1. Pointeurs intelligents et leur fonctionnement interne
Question : "Expliquez la différence entre std::unique_ptr et std::shared_ptr ? Comment la sécurité des threads du compteur de références est-elle implémentée ?"
C'est un classique. L'intervieweur veut voir que vous comprenez non seulement la syntaxe, mais aussi la sémantique de la gestion des ressources.
- std::unique_ptr — propriété exclusive. Non copiable, uniquement déplaçable. Idéal pour les ressources qui ont un seul propriétaire.
- std::shared_ptr — propriété partagée. Utilise un compteur de références (bloc de contrôle). Lorsque le compteur tombe à zéro, la ressource est détruite.
- std::weak_ptr — "observateur". N'augmente pas le compteur de références. Utilisé pour briser les dépendances cycliques dans
shared_ptr.
Nuance avancée : Le compteur de références dans shared_ptr est thread-safe (des opérations atomiques sont utilisées), mais cela ne rend pas l'objet géré lui-même thread-safe. Il existe également deux compteurs : les références fortes (strong) et les références faibles (weak). Tant qu'il y a au moins une référence faible, le bloc de contrôle n'est pas supprimé, même si l'objet est déjà détruit.
#include <memory>#include <iostream>
class Resource {public: Resource() { std::cout << "Resource acquired"; } ~Resource() { std::cout << "Resource destroyed"; } void doWork() { std::cout << "Working..."; }};
int main() { std::unique_ptr<Resource> uptr = std::make_unique<Resource>(); uptr->doWork(); // std::unique_ptr<Resource> uptr2 = uptr; // Erreur de compilation ! std::unique_ptr<Resource> uptr2 = std::move(uptr); // Ok
std::shared_ptr<Resource> sptr1 = std::make_shared<Resource>(); std::shared_ptr<Resource> sptr2 = sptr1; // Compteur de références = 2 sptr1.reset(); // Compteur = 1 sptr2.reset(); // Compteur = 0, objet détruit return 0;}2. Tables virtuelles (vtable) et mécanisme de liaison tardive
Question : "Comment fonctionne une fonction virtuelle au niveau de la mémoire ? Où est stockée la vtable ?"
Cette question teste la compréhension non seulement du polymorphisme, mais aussi de l'organisation de la mémoire en C++.
- Pour chaque classe ayant au moins une fonction virtuelle, le compilateur crée une table statique (vtable). Elle contient des pointeurs vers les fonctions virtuelles.
- Chaque objet d'une telle classe contient un pointeur caché
__vptr, qui pointe vers la vtable de sa classe. - Lors de l'appel d'une fonction virtuelle via un pointeur ou une référence, le compilateur génère du code qui suit
__vptrdans la vtable, trouve la fonction appropriée par décalage et l'appelle. - La vtable est stockée en mémoire statique (comme une variable globale), et non sur la pile ou le tas.
Nuance avancée : Complexités avec l'héritage multiple. Dans ce cas, un objet peut avoir plusieurs __vptr, et la nécessité de fonctions thunk (ajustement du pointeur this) apparaît. C'est l'une des raisons pour lesquelles l'héritage multiple en C++ est considéré comme dangereux et lent.
#include <iostream>
class Base {public: virtual void foo() { std::cout << "Base::foo"; } virtual ~Base() = def