Pourquoi le C++ fait-il encore peur même aux seniors ?
\nLe C++ est un langage qui vous donne un contrôle total sur la mémoire, mais seulement jusqu'à ce que vous oubliiez un point-virgule ou que vous appeliez accidentellement une fonction virtuelle depuis un constructeur. Les entretiens C++ sont réputés pour leur difficulté : on peut vous interroger sur les champs de bits, le fonctionnement de la table virtuelle, ou les subtilités de std::move. Si vous préparez un entretien technique, cet article est votre checklist. Nous allons décortiquer les sujets les plus retors qui surgiront presque à coup sûr lors de l'entretien, et vous montrer comment y répondre pour ne pas échouer.
Sur le portail éducatif PythonLib, nous discutons souvent de la transition entre Python et C++ et vice-versa. Mais aujourd'hui, c'est du C++ pur. Aucun compromis.
\n\n1. Fonctions virtuelles et tables VTable : comment éviter le piège
\nQuestion : "Que se passe-t-il si on appelle une fonction virtuelle depuis le constructeur de la classe de base ?" — un grand classique. 70% des candidats répondent incorrectement. Analysons cela.
\n\nPourquoi est-ce dangereux ?
\nLorsque vous appelez une fonction virtuelle dans le constructeur de la classe de base, l'objet de la classe dérivée n'est pas encore créé. La table virtuelle (VTable) pointe à ce moment vers les méthodes de la classe de base, et non de la classe dérivée. En conséquence, le polymorphisme ne fonctionne pas.
\n\n#include <iostream>\n\nclass Base {\npublic:\n Base() { callFoo(); } // Appel dans le constructeur\n virtual void foo() { std::cout << "Base::foo\\n"; }\n void callFoo() { foo(); }\n};\n\nclass Derived : public Base {\npublic:\n Derived() {}\n virtual void foo() override { std::cout << "Derived::foo\\n"; }\n};\n\nint main() {\n Derived d; // Affichera : Base::foo, et non Derived::foo\n return 0;\n}\n\nConclusion : n'appelez jamais de fonctions virtuelles dans les constructeurs et les destructeurs. Si l'intervieweur vous le demande, expliquez sans crainte le mécanisme de la VTable.
\n\n2. Pointeurs intelligents : RAII et gestion de la mémoire
\nAucun entretien C++ sérieux ne fait l'impasse sur ce sujet. Les questions sur std::unique_ptr, std::shared_ptr et std::weak_ptr sont la base. Mais on interroge souvent sur les références cycliques et std::enable_shared_from_this.
Comment éviter une fuite mémoire avec shared_ptr ?
\nImaginez que vous ayez deux objets qui se référencent mutuellement via shared_ptr. Le compteur de références ne sera jamais remis à zéro — la mémoire fuit. Solution : l'un des pointeurs doit être un weak_ptr.
#include <memory>\n#include <iostream>\n\nstruct Node {\n std::shared_ptr<Node> next;\n std::weak_ptr<Node> prev; // Référence faible pour éviter le cycle\n ~Node() { std::cout << "Node destroyed\\n"; }\n};\n\nint main() {\n auto a = std::make_shared<Node>();\n auto b = std::make_shared<Node>();\n a->next = b;\n b->prev = a; // weak_ptr n'augmente pas le compteur\n // En sortant de la portée, les deux nœuds sont correctement supprimés\n return 0;\n}\n\nLors de l'entretien, mentionnez impérativement le concept RAII (Resource Acquisition Is Initialization) — c'est la philosophie du C++ qui lie la durée de vie d'une ressource à celle d'un objet.
\n\n3. Sémantique de déplacement et std::move : magie ou piège ?
\nQuestion typique : "Que fait std::move ?" Répondre "il déplace l'objet" est incorrect. std::move ne fait que convertir le type en une référence rvalue. Le déplacement réel a lieu dans le constructeur de déplacement ou l'opérateur d'affectation de déplacement.
Exemple pour l'entretien
\n#include <string>\n#include <vector>\n#include <iostream>\n\nclass MyString {\n std::string data;\npublic:\n MyString(const std::string& s) : data(s) {}\n // Constructeur de déplacement\n MyString(MyString&& other) noexcept : data(std::move(other.data)) {\n std::cout << "Move constructor c