Preparación Junior C++

Preguntas de entrevista Junior para desarrolladores backend C++

15 preguntas seleccionadas para desarrolladores Junior C++ que necesitan explicar fundamentos del lenguaje y gestión básica de recursos con claridad.

Empezar una entrevista IA Junior 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

Sistema de tipos

3¿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

4Describe 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

5¿Qué es std::byte y cómo deben representarse de forma segura los buffers binarios sin procesar en C++?

std::byte es un tipo distinto para representar datos binarios sin procesar como bytes, no caracteres ni enteros aritméticos. Mejora la seguridad de tipos porque los buffers de bytes no se tratan accidentalmente como texto o valores numéricos, aunque sigue soportando operaciones bit a bit. Los buffers binarios sin procesar normalmente deben representarse con almacenamiento orientado a bytes como std::vector<std::byte> o std::array<std::byte, N>, y pasarse mediante APIs no propietarias como std::span<std::byte> o std::span<const std::byte>. El código de serialización debe codificar y decodificar valores explícitamente en lugar de depender de disposiciones arbitrarias de objetos.

Probar responder esta pregunta con un coach de IA

Vida útil de objetos

6Describe 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

Manejo de errores

7Explica el manejo de excepciones en C++, el desenrollado de pila, la interacción con destructores y los límites de excepciones en servicios.

Las excepciones de C++ transfieren el control desde una expresión `throw` al `catch` coincidente más cercano. Durante la propagación, el desenrollado de pila destruye los objetos automáticos completamente construidos en orden inverso, por lo que la limpieza RAII se ejecuta automáticamente. En general, los destructores no deberían lanzar; si una excepción escapa de un destructor `noexcept` o si otra excepción escapa durante un desenrollado activo, el programa llama a `std::terminate`. Las excepciones normalmente deben capturarse por referencia, comúnmente `const&`, para evitar slicing y copias innecesarias. Los servicios backend deben definir límites de excepción, como manejadores de solicitudes, puntos de entrada de hilos de trabajo, callbacks de frameworks RPC/HTTP y `main`, donde las excepciones se registran, se convierten en respuestas de error o códigos de estado y se evita que escapen a contextos inapropiados como APIs de C, destructores, hilos o funciones `noexcept`.

Probar responder esta pregunta con un coach de IA

8¿Qué ocurre si un destructor lanza, y cómo deberían los tipos backend informar fallos de limpieza?

Los destructores son implícitamente `noexcept(true)` en los casos normales, así que si una excepción escapa de un destructor de ese tipo, se llama a `std::terminate`. Incluso si un destructor se declara explícitamente `noexcept(false)`, lanzar durante el desenrollado de pila es peligroso porque una segunda excepción que escapa mientras otra excepción está activa también termina el programa. Por tanto, los destructores deberían realizar limpieza de mejor esfuerzo y no deberían dejar escapar excepciones. Los tipos backend deberían informar los fallos de limpieza mediante operaciones explícitas como `close()`, `flush()`, `commit()`, `stop()` o `shutdown()` que devuelvan un error/`expected` o lancen antes de la destrucción. El destructor puede registrar, emitir métricas, suprimir errores o realizar limpieza de fallback segura, pero no debería ser el canal principal de informe de errores accionables.

Probar responder esta pregunta con un coach de IA

Biblioteca estándar

9¿Cómo gestiona std::vector la capacidad, el crecimiento, la realocación y la estabilidad de iteradores?

std::vector almacena elementos de forma contigua y lleva tanto size como capacity. size es el número de elementos construidos; capacity es la cantidad de almacenamiento de elementos asignado disponible antes de que se necesite otra asignación. Cuando añadir elementos excedería la capacity, vector asigna un bloque más grande, típicamente usando una estrategia de crecimiento geométrico definida por la implementación, mueve o copia los elementos existentes, destruye los antiguos y libera el almacenamiento antiguo. reserve(n) aumenta la capacity sin cambiar size, mientras que resize(n) cambia size construyendo o destruyendo elementos. La realocación invalida todos los iteradores, referencias y punteros a elementos; incluso sin realocación, operaciones como insert y erase pueden invalidar posiciones en o después del punto de modificación.

Probar responder esta pregunta con un coach de IA

10¿Qué reglas de invalidación deberías conocer para contenedores estándar contiguos y basados en nodos?

Las reglas de invalidación dependen del contenedor y de la operación. Los contenedores contiguos como vector y string tienen una estabilidad frágil de iteradores/referencias: el crecimiento puede realocar e invalidar todos los iteradores, referencias y punteros, e insert/erase pueden desplazar elementos e invalidar posiciones en o después del cambio incluso sin realocación. Los contenedores ordenados basados en nodos como list, map, set y sus variantes multi generalmente mantienen estables los iteradores y referencias a elementos existentes no borrados durante insert; borrar un elemento invalida el iterador/referencia a ese elemento borrado. Los contenedores unordered también almacenan elementos en nodos, por lo que las referencias y punteros a elementos generalmente permanecen estables durante rehash, pero rehash invalida iteradores. deque tiene reglas especiales de almacenamiento segmentado. En la práctica, revisa el contenedor y la operación específicos antes de guardar iteradores o referencias a través de modificaciones.

Probar responder esta pregunta con un coach de IA

11Compara std::map, std::unordered_map y contenedores de estilo flat-map para tablas de búsqueda en backend.

std::map es un contenedor asociativo ordenado, normalmente basado en árbol, con búsqueda/inserción/borrado logarítmicos; es útil cuando importan la iteración ordenada, las consultas por rango o las garantías de orden. std::unordered_map está basado en una tabla hash, con operaciones por clave exacta de tiempo constante en promedio y sin ordenación de claves; a menudo es una buena opción por defecto para tablas de búsqueda grandes y mutables cuando el hashing es bueno. Un contenedor de estilo flat-map almacena pares clave/valor ordenados de forma contigua, lo que proporciona buena localidad de caché e iteración/búsqueda binaria rápidas, pero la inserción y el borrado en medio son lineales. Para tablas de búsqueda en backend, elige según si la carga de trabajo necesita ordenación/rangos, principalmente búsquedas exactas, mutación frecuente, latencia predecible, sobrecarga de memoria y comportamiento de caché.

Probar responder esta pregunta con un coach de IA

12Explica std::optional y casos de uso típicos en backend para representar valores ausentes.

std::optional<T> representa o bien un valor T contenido o bien ningún valor. El estado vacío se representa con std::nullopt; el código puede comprobar has_value() o usar el optional en contexto booleano, acceder al valor con * o value(), y proporcionar un valor por defecto con value_or(). En código backend es útil para campos de base de datos anulables, campos opcionales de solicitud/configuración, fallos de caché o repositorio donde la ausencia es esperada, y estados de dominio donde un centinela como -1 o una cadena vacía sería ambiguo. Modela la ausencia de un valor, no polimorfismo ni información de error rica.

Probar responder esta pregunta con un coach de IA

13¿Qué son std::string_view y std::span, y qué peligros de vida introducen las vistas no propietarias?

std::string_view es una vista no propietaria de una secuencia contigua de caracteres; std::span<T> es una vista no propietaria de una secuencia contigua de T. Son útiles para parámetros de copia cero y APIs de buffers porque llevan un puntero y una longitud sin asignar ni poseer memoria. El principal peligro es la vida: el almacenamiento referenciado debe vivir más que la vista y no debe invalidarse mientras se usa la vista. Devolver o almacenar una vista a un temporal, objeto local, objeto destruido o contenedor realocado puede dejar una vista colgante.

Probar responder esta pregunta con un coach de IA

Concurrencia

14¿En qué se diferencian std::mutex, std::shared_mutex y std::recursive_mutex, y cuándo elegirías cada uno?

std::mutex proporciona bloqueo exclusivo: solo un hilo puede poseerlo, por lo que es la opción por defecto para proteger estado mutable compartido. std::shared_mutex soporta locks compartidos de lectores y locks exclusivos de escritores: muchos lectores pueden mantener el lock concurrentemente, pero los escritores necesitan acceso exclusivo. Elígelo para datos mayoritariamente de lectura cuando la concurrencia de lectores compense la sobrecarga y cuando la equidad/inanición de escritores sea aceptable o esté gestionada. std::recursive_mutex es un mutex exclusivo que el mismo hilo puede bloquear múltiples veces y debe desbloquear el mismo número de veces; úsalo raramente, principalmente para código heredado o reentrante, porque puede ocultar un mal diseño de bloqueo.

Probar responder esta pregunta con un coach de IA

Características del lenguaje

15Explica los modos de captura de lambdas y cómo interactúan las capturas con los tiempos de vida de los objetos en callbacks.

Una lambda puede capturar variables por valor (`[x]` o `[=]`), por referencia (`[&x]` o `[&]`), capturar `this` o usar init-capture como `[p = std::move(ptr)]`. Las capturas por valor copian el objeto capturado dentro del closure cuando se crea la lambda; las capturas por referencia almacenan referencias, por lo que los objetos originales deben sobrevivir a todas las invocaciones de la lambda. En callbacks, lambdas almacenadas o trabajo asíncrono, las capturas por referencia y las capturas de `this` son peligrosas porque las variables locales o el objeto pueden destruirse antes de la invocación. Prefiere capturas por valor para los datos necesarios, move/init-capture para propiedad, o patrones deliberados con `shared_ptr`/`weak_ptr` cuando el tiempo de vida del objeto debe extenderse o comprobarse.

Probar responder esta pregunta con un coach de IA