Preparación entrevista C++

Preguntas de entrevista para desarrolladores backend C++

Preguntas seleccionadas para desarrolladores backend C++, agrupadas por tema y tomadas del mismo catálogo que impulsa la práctica de EngineerSpeak.

Empezar una entrevista IA de C++No se requiere tarjeta. 1 sesión gratuita disponible.
Práctica de entrevista técnica en inglésDiseñado para hablantes no nativos que desean practicar entrevistas técnicas en inglés.

Resource Management

1Explica RAII y cómo determina la gestión de recursos segura ante excepciones en servicios backend en C++.

RAII (Resource Acquisition Is Initialization) significa que un objeto de C++ posee un recurso y lo libera desde su destructor. Como los objetos locales se destruyen automáticamente cuando termina su vida útil/ámbito, incluso durante el desenrollado de la pila por excepciones, RAII proporciona limpieza determinista y hace que las rutas de error sean seguras ante excepciones. En servicios backend esto se aplica no solo a la memoria, sino también a descriptores de archivo, sockets, bloqueos de mutex, handles de base de datos, transacciones y otros recursos del sistema operativo o de la aplicación.

Probar responder esta pregunta con un coach de IA

Gestión de memoria

2Compara std::unique_ptr y std::shared_ptr y describe cuándo es apropiado usar cada uno en APIs backend.

std::unique_ptr representa propiedad exclusiva: es barato, movible pero no copiable, y es apropiado para un único propietario o para APIs que transfieren propiedad. std::shared_ptr representa propiedad compartida: es copiable y mantiene vivo un objeto mediante conteo de referencias hasta que el último propietario fuerte lo libera. En APIs backend, usa unique_ptr cuando se transfiere la propiedad, shared_ptr solo cuando varios propietarios independientes deben extender la vida útil, y referencias o punteros sin procesar para acceso sin propiedad. std::make_shared suele preferirse al crear objetos shared_ptr porque es eficiente y seguro ante excepciones.

Probar responder esta pregunta con un coach de IA

3¿Cómo funcionan los deleters personalizados en smart pointers, y cuándo son útiles para la interoperabilidad C/C++?

Un deleter personalizado es lógica de limpieza invocable que usa un smart pointer en lugar de la operación delete predeterminada cuando el puntero libera su recurso poseído. Es útil para la interoperabilidad C/C++ cuando un recurso debe liberarse con una función específica como fclose, close, curl_easy_cleanup, SSL_free, free o una función destroy de biblioteca. Para unique_ptr, el tipo del deleter forma parte del tipo de unique_ptr y puede afectar a su tamaño; los deleters sin estado pueden optimizarse hasta no ocupar espacio, mientras que los deleters puntero a función o con estado añaden almacenamiento. Para shared_ptr, el deleter se almacena en el bloque de control y se ejecuta cuando el último propietario fuerte libera el objeto. Los deleters personalizados permiten que los recursos de C participen en RAII de forma segura.

Probar responder esta pregunta con un coach de IA

4Explica weak_ptr y la mecánica del bloque de control de shared_ptr, incluidos ciclos, enable_shared_from_this y costes de conteo de referencias.

Un shared_ptr gestiona la propiedad compartida mediante un bloque de control que contiene contadores de referencias fuertes y débiles, además de información de limpieza como el deleter y el asignador. Copiar o destruir objetos shared_ptr incrementa o decrementa el contador fuerte, normalmente con operaciones atómicas, por lo que objetos shared_ptr separados pueden manipularse de forma segura entre hilos, pero cada actualización del contador de referencias tiene coste. Cuando el contador fuerte llega a cero, el objeto gestionado se destruye; el bloque de control permanece hasta que las referencias débiles también desaparecen. weak_ptr apunta al mismo bloque de control sin extender la vida útil del objeto; lock() devuelve un shared_ptr si el objeto sigue vivo y un shared_ptr vacío en caso contrario. Los ciclos formados solo por shared_ptr fugan memoria porque los contadores fuertes nunca llegan a cero, así que weak_ptr se usa para punteros inversos o enlaces observadores. enable_shared_from_this permite que un objeto que ya está poseído por shared_ptr cree un nuevo shared_ptr a sí mismo usando el bloque de control existente, evitando bloques de control separados peligrosos. Los costes de conteo de referencias incluyen incrementos/decrementos atómicos, contención de caché, asignación del bloque de control y sobrecarga en rutas críticas.

Probar responder esta pregunta con un coach de IA

Sistema de tipos

5¿Qué es la semántica de movimiento y cómo implementas correctamente la construcción por movimiento y la asignación por movimiento?

La semántica de movimiento permite a C++ transferir recursos desde objetos temporales o prescindibles en lugar de copiarlos. Usa referencias rvalue como T&& y std::move, que es un cast que permite seleccionar sobrecargas de movimiento; std::move no mueve nada por sí mismo. Un constructor de movimiento correcto inicializa un objeto nuevo tomando el recurso del objeto origen y dejando el origen válido, destructible y asignable. Un operador de asignación por movimiento correcto transfiere a un objeto existente, maneja o tolera la autoasignación, libera o reutiliza el recurso actual del destino, toma el recurso del origen y deja el origen seguro. Las operaciones de movimiento a menudo deberían ser noexcept para que los contenedores estándar puedan usarlas durante la reasignación preservando las garantías ante excepciones.

Probar responder esta pregunta con un coach de IA

6Describe la corrección de const en APIs de C++ y cómo diseñar funciones miembro const de forma efectiva.

La corrección de const significa expresar mediante el sistema de tipos qué operaciones no modifican el estado observable o lógico de un objeto. Una función miembro const tiene un objeto this calificado como const, por lo que no puede modificar miembros de datos no mutable ni llamar a funciones miembro no const sobre el mismo objeto. Un buen diseño de API marca como const las consultas de solo lectura, devuelve valores o referencias/punteros const cuando corresponde y evita exponer estado interno mutable desde funciones const. mutable debe reservarse para detalles de implementación que no cambian el estado lógico, como cachés, valores perezosos, métricas o mutexes. const es un contrato de API sobre mutación, no una garantía automática de seguridad de hilos; las garantías de concurrencia necesitan implementación y documentación separadas.

Probar responder esta pregunta con un coach de IA

7¿Qué son los tipos move-only y cómo influyen en los límites de API para sockets, handles de archivo y locks?

Los tipos move-only son tipos que no pueden copiarse pero sí pueden moverse. Son comunes para propiedad única de recursos como sockets, descriptores de archivo, handles de archivo, locks y `std::unique_ptr`. Las operaciones de copia se eliminan para evitar propiedad duplicada y doble liberación; las operaciones de move transfieren la propiedad y dejan la fuente válida pero normalmente vacía/no propietaria. Las APIs deben hacer explícitos los límites de propiedad: las fábricas pueden devolver objetos move-only por valor, las funciones que toman propiedad pueden aceptar por valor o referencia rvalue, y las funciones que solo inspeccionan deben tomar referencias, punteros u otros handles prestados. Los contenedores pueden almacenar valores move-only cuando los elementos se insertan o reubican mediante move.

Probar responder esta pregunta con un coach de IA

8Compara auto, decltype, decltype(auto) y la deducción de argumentos de plantilla en código backend común.

auto usa una deducción similar a la de plantillas para variables: auto simple normalmente descarta referencias y const de nivel superior salvo que la declaración las solicite, como auto&, const auto& o auto&&. decltype(expr) inspecciona de forma más exacta el tipo declarado o el tipo de la expresión: una id-expression sin paréntesis da el tipo declarado, mientras que otras expresiones lvalue producen T&, los xvalues producen T&& y los prvalues producen T. decltype(auto) deduce usando las reglas de decltype, a menudo para tipos de retorno cuando deben preservarse referencias. La deducción de argumentos de plantilla es similar a auto, pero depende de la forma del parámetro, como T, T&, const T& o T&&, y tiene sus propias reglas. Los inicializadores entre llaves son una diferencia común: auto x = {1,2} deduce std::initializer_list<int>, mientras que un parámetro de plantilla simple generalmente no puede deducir T a partir de un inicializador entre llaves desnudo salvo que el parámetro espere un initializer_list u otro tipo adecuado.

Probar responder esta pregunta con un coach de IA

Vida útil de objetos

9Describe la Rule of Zero, la Rule of Three y la Rule of Five, y cuándo se aplica cada una.

Rule of Zero: prefiere clases que no declaren destructor ni operaciones de copia/movimiento personalizados; deja que miembros RAII como std::string, std::vector, std::unique_ptr, envoltorios de archivos/sockets, etc. gestionen los recursos. Rule of Three: si una clase gestiona manualmente un recurso y necesita un destructor, constructor de copia u operador de asignación por copia personalizado, normalmente necesita los tres para definir correctamente el comportamiento de copia/propiedad. Rule of Five: en C++11 y posteriores, esos tipos también deberían considerar el constructor de movimiento y el operador de asignación por movimiento. Usa la Rule of Zero para la mayoría de los tipos de aplicación; usa la Rule of Three/Five cuando el tipo posee directamente un recurso o tiene semánticas de propiedad/ciclo de vida no triviales.

Probar responder esta pregunta con un coach de IA

Semántica del lenguaje

10Describe las formas de inicialización en C++ y los errores comunes, incluidos initializer_list y agregados.

C++ tiene varias formas de inicialización. La inicialización por defecto, como `T x;`, llama a un constructor por defecto para tipos de clase pero deja sin inicializar las variables automáticas fundamentales. La inicialización por valor, como `T x{};` o `T()`, inicializa a cero cuando corresponde antes de la inicialización por constructor/miembros. La inicialización con lista usa llaves, rechaza conversiones con estrechamiento y tiene reglas especiales de resolución de sobrecargas, incluida una fuerte preferencia por constructores `std::initializer_list` viables. La inicialización de agregados inicializa directamente miembros de agregados con llaves; C++20 también soporta inicializadores designados para agregados, en orden de declaración. Los errores comunes incluyen escalares locales sin inicializar, selección sorprendente de sobrecargas `initializer_list`, errores de estrechamiento con llaves, el most-vexing parse con paréntesis y cambios de comportamiento cuando un tipo deja de ser un agregado.

Probar responder esta pregunta con un coach de IA

11¿Qué es el comportamiento indefinido en C++ y cómo puede aparecer en incidentes de producción backend?

El comportamiento indefinido es un comportamiento para el cual el estándar de C++ no impone ningún requisito después de que ocurre una operación inválida. El programa puede parecer funcionar, fallar, corromper datos, exponer fallos de seguridad u optimizarse hasta producir un comportamiento sorprendente. Los compiladores asumen que el UB no ocurre y optimizan basándose en esa suposición, por lo que los problemas pueden aparecer solo en compilaciones de lanzamiento o bajo tráfico de producción. Los incidentes backend pueden provenir de punteros/referencias colgantes, use-after-free, violaciones de ciclo de vida de objetos, acceso fuera de límites, overflow de enteros con signo, data races, casts inválidos, lecturas no inicializadas, dobles free o violaciones de strict aliasing. La mitigación incluye RAII y un diseño claro de propiedad/ciclo de vida, abstracciones más seguras y comprobaciones de límites, pruebas/fuzzing, revisión de código, análisis estático y sanitizers como ASan, UBSan y TSan.

Probar responder esta pregunta con un coach de IA

Object Model

12Explica las funciones virtuales, vtables, coste del despacho dinámico, object slicing y riesgos de destructores virtuales.

Una función virtual habilita polimorfismo en tiempo de ejecución: cuando se llama a través de un puntero o referencia base, se selecciona la implementación del tipo dinámico del objeto. La mayoría de implementaciones almacenan un vptr oculto en cada objeto polimórfico que apunta a una vtable de direcciones de funciones virtuales para ese tipo dinámico. El coste típico es un puntero extra en el objeto, una llamada indirecta, posible impacto en caché/predicción de saltos y menores oportunidades de inlining, aunque los compiladores a veces pueden desvirtualizar. Object slicing ocurre cuando un objeto derivado se copia o almacena por valor como un objeto base, perdiendo la parte derivada y el comportamiento dinámico. Si una clase base está destinada a ser eliminada a través de un puntero base, su destructor debe ser virtual; de lo contrario, eliminar un objeto derivado a través de ese puntero base tiene comportamiento indefinido.

Probar responder esta pregunta con un coach de IA

Biblioteca estándar

13Describe los relojes, puntos temporales y duraciones de std::chrono para timeouts de servicios, métricas y marcas de tiempo.

`std::chrono` modela el tiempo con relojes, `time_point`s y `duration`s. Una `duration` es un intervalo con una unidad, como milisegundos o segundos. Un `time_point` es un punto en la línea temporal de un reloj específico. `steady_clock` es monotónico y debe usarse para tiempo transcurrido, timeouts de servicios, plazos límite y mediciones de latencia porque no se ve afectado por cambios del reloj de pared. `system_clock` representa tiempo civil/de pared y es apropiado para marcas de tiempo, logging, persistencia y conversión de calendario, pero puede saltar cuando se ajusta la hora del sistema. Los timeouts normalmente deben basarse en `steady_clock::now() + duration`; las marcas de tiempo de máquina deben usar un formato de tiempo de pared claramente documentado, normalmente UTC en los límites de API/almacenamiento.

Probar responder esta pregunta con un coach de IA

14Describe std::format y las facilidades modernas de formateo frente a iostreams y APIs estilo printf.

`std::format` es la facilidad de formateo type-safe de C++20 inspirada en fmtlib. Usa campos de reemplazo `{}` y especificaciones de formato para producir texto formateado sin la sintaxis de inserción con estado de iostreams ni los varargs C estilo `printf`. Comparado con `printf`, evita muchos problemas de desajuste entre formato y tipo; comparado con iostreams, a menudo es más claro y fácil de componer. fmtlib es la biblioteca ampliamente usada que precedió e influyó en `std::format` y puede proporcionar soporte más amplio o características más nuevas. Los tipos definidos por el usuario pueden formatearse con soporte de formatter personalizado. En logging de alto volumen, el rendimiento depende de evitar formateo, conversiones y asignaciones innecesarias, especialmente para niveles de log deshabilitados; son preferibles APIs que difieren el formateo o comprueban primero el nivel de log.

Probar responder esta pregunta con un coach de IA

Templates

15Explica las categorías de valor de C++ y el perfect forwarding, y por qué importan para APIs genéricas eficientes.

Las categorías de valor de C++ describen expresiones: los lvalues tienen identidad y pueden referenciarse después de la expresión; los prvalues son rvalues puros, como muchos temporales/valores calculados; los xvalues son objetos en expiración cuyos recursos pueden reutilizarse. El perfect forwarding es la técnica de plantillas que consiste en recibir una referencia de reenvío, normalmente T&& donde T se deduce, y reenviar con std::forward<T>(arg) para preservar la categoría de valor del llamador: los lvalues siguen siendo lvalues y los rvalues siguen siendo rvalues. Las reglas de colapso de referencias hacen esto posible. Importa para APIs backend genéricas porque envoltorios, factorías, despachadores y funciones estilo emplace pueden evitar copias innecesarias y preservar la selección de sobrecargas y el comportamiento de movimiento.

Probar responder esta pregunta con un coach de IA

Concurrencia

16Describe la prevención de deadlocks y estrategias de bloqueo de múltiples mutex usando std::scoped_lock y std::lock.

Los deadlocks se previenen evitando esperas circulares: adquiere locks en un orden global coherente cuando sea posible, o adquiere múltiples mutexes con `std::lock`/`std::scoped_lock`, que usan un algoritmo de evitación de deadlocks. `std::scoped_lock lock(a, b, ...)` es la forma RAII más simple para bloquear varios mutexes y desbloquearlos automáticamente al salir del ámbito. Con `std::lock`, primero bloquea los mutexes y luego adjunta envoltorios RAII usando `std::adopt_lock`, o usa `std::unique_lock` con `std::defer_lock`. Mantén las secciones críticas cortas y evita operaciones bloqueantes o callbacks desconocidos mientras mantienes locks; la evitación de deadlocks no garantiza automáticamente equidad ni previene todos los escenarios de livelock/inanición.

Probar responder esta pregunta con un coach de IA

17Explica los patrones de uso de condition_variable para esperar cambios de estado.

Usa `std::condition_variable` para esperar a que cambie un predicado de estado protegido por un mutex. El estado compartido se modifica manteniendo el mismo mutex, y luego `notify_one` o `notify_all` despierta a los hilos en espera. Los hilos en espera deben usar `cv.wait(lock, predicate)` o un bucle equivalente porque los despertares pueden ser espurios y las notificaciones no se recuerdan independientemente del estado del predicado. `notify_one` despierta a un hilo en espera; `notify_all` despierta a todos los hilos en espera y es apropiado para cambios de estado de difusión, como el apagado.

Probar responder esta pregunta con un coach de IA

18Diseña una cola acotada segura para hilos usando primitivas estándar de C++ y define las garantías de su API.

Una cola acotada segura para hilos puede construirse con un `std::mutex`, variables de condición, un contenedor de capacidad fija y una marca de apagado/cerrado. `push` se bloquea, expira por timeout o falla cuando la cola está llena, lo que proporciona backpressure; `pop` se bloquea cuando está vacía. Ambas operaciones deben esperar sobre predicados como `size < capacity || closed` y `!empty || closed`. En apagado/cierre, despierta a productores y consumidores bloqueados; rechaza nuevos `push` y define si los consumidores vacían los elementos existentes o se detienen inmediatamente. La API debe especificar el comportamiento de bloqueo, la semántica de cierre, los valores de retorno y las garantías de concurrencia.

Probar responder esta pregunta con un coach de IA

Database

19¿Cómo ejecutas una migración de esquema para un servicio en C++ sin tiempo de inactividad?

Usa una migración por etapas expand-contract. Primero realiza adiciones de esquema compatibles hacia atrás, como columnas nullable o tablas nuevas, que no rompan el servicio C++ que está en ejecución. Despliega código compatible que pueda manejar tanto la representación antigua como la nueva, rellena (backfill) los datos existentes en lotes pequeños y limitados, valida la consistencia, cambia las lecturas al nuevo esquema y solo después elimina columnas o código antiguos cuando todas las versiones desplegadas ya no dependan de ellos. En tablas grandes, evita DDL bloqueante prolongado, usa operaciones online/concurrentes cuando estén soportadas, monitoriza locks/replicación/recursos y mantén rutas de reversión que funcionen con código parcialmente desplegado y datos parcialmente migrados.

Probar responder esta pregunta con un coach de IA

20¿Cómo debería un servicio en C++ leer de réplicas de base de datos gestionando lecturas obsoletas y garantías de read-your-writes?

Trata las lecturas de réplicas como un trade-off explícito de consistencia. Las lecturas que toleran obsolescencia pueden ir a réplicas sanas, pero las lecturas read-your-writes, transaccionales o críticas en frescura deben ir al primary salvo que se sepa que la réplica elegida ha reproducido la posición de escritura relevante, como un LSN, timestamp o versión. Controla el lag y la salud de las réplicas, codifica la consistencia requerida por endpoint o por petición, y haz fallback al primary, espera a que se ponga al día o falla cuando las réplicas superen la obsolescencia permitida. Documenta el modelo de consistencia para que los llamadores sepan qué lecturas pueden estar obsoletas.

Probar responder esta pregunta con un coach de IA