¿Por qué C++ sigue asustando incluso a los seniors?
\nC++ es un lenguaje que te da control total sobre la memoria, pero solo hasta que olvidas poner un punto y coma o accidentalmente llamas a una función virtual desde un constructor. Las entrevistas de C++ son famosas por su dureza: te pueden preguntar sobre campos de bits, el funcionamiento de la tabla virtual y los detalles de std::move. Si te estás preparando para una entrevista técnica, este artículo es tu lista de verificación. Analizaremos los temas más engañosos que casi con seguridad surgirán en la entrevista y te mostraremos cómo responder para no fracasar.
En el portal educativo PythonLib a menudo discutimos cómo migrar de Python a C++ y viceversa. Pero hoy es C++ puro. Sin concesiones.
\n\n1. Funciones virtuales y tablas VTable: cómo no caer en la trampa
\nPregunta: "¿Qué sucede si se llama a una función virtual desde el constructor de la clase base?" — es un clásico del género. El 70% de los candidatos responde incorrectamente. Analicémoslo.
\n\n¿Por qué es peligroso?
\nCuando llamas a una función virtual en el constructor de la clase base, el objeto de la clase derivada aún no se ha creado. La tabla virtual (VTable) en ese momento apunta a los métodos de la clase base, no a los de la derivada. Como resultado, el polimorfismo no funciona.
\n\n#include <iostream>\n\nclass Base {\npublic:\n Base() { callFoo(); } // Llamada en el constructor\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; // Imprimirá: Base::foo, no Derived::foo\n return 0;\n}\n\nConclusión: nunca llames a funciones virtuales en constructores y destructores. Si el entrevistador pregunta, explica con confianza el mecanismo de VTable.
\n\n2. Punteros inteligentes: RAII y gestión de memoria
\nNinguna entrevista seria de C++ omite este tema. Las preguntas sobre std::unique_ptr, std::shared_ptr y std::weak_ptr son básicas. Pero a menudo preguntan sobre referencias cíclicas y std::enable_shared_from_this.
¿Cómo evitar una fuga de memoria con shared_ptr?
\nImagina que tienes dos objetos que se referencian mutuamente a través de shared_ptr. El contador de referencias nunca llegará a cero — la memoria se fuga. Solución: uno de los punteros debe ser weak_ptr.
#include <memory>\n#include <iostream>\n\nstruct Node {\n std::shared_ptr<Node> next;\n std::weak_ptr<Node> prev; // Referencia débil para evitar el ciclo\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 no incrementa el contador\n // Al salir del ámbito, ambos nodos se eliminan correctamente\n return 0;\n}\n\nEn la entrevista, menciona obligatoriamente el concepto RAII (Resource Acquisition Is Initialization) — es la filosofía de C++ que vincula el tiempo de vida de un recurso con el tiempo de vida de un objeto.
\n\n3. Move-semántica y std::move: ¿magia o trampa?
\nPregunta típica: "¿Qué hace std::move?" La respuesta "mueve el objeto" es incorrecta. std::move simplemente convierte el tipo a una referencia rvalue. El movimiento real ocurre en el constructor de movimiento o en el operador de asignación de movimiento.
Ejemplo para la entrevista
\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 // Constructor de movimiento\n MyString(MyString&& other) noexcept : data(std::move(other.data)) {\n std::cout << "Move constructor c