Как пройти собеседование по C++: топ вопросов и ответов

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как пройти собеседование по C++: разбор топ-вопросов с примерами

Собеседование на позицию C++ разработчика — это проверка не только синтаксиса, но и глубокого понимания того, как работает память, компилятор и стандартная библиотека. В этой статье мы разберем самые частые вопросы, которые задают на интервью в 2024-2025 годах. Вы узнаете не просто правильные ответы, но и увидите примеры кода, которые помогут вам уверенно пройти техническое интервью.

1. Виртуальные функции и полиморфизм

Это классика. Интервьюеры хотят убедиться, что вы понимаете разницу между статическим и динамическим связыванием, а также механизм работы vtable (таблицы виртуальных функций).

Вопрос: Что произойдет, если вызвать виртуальную функцию из конструктора или деструктора?

Ответ: Вызов виртуальной функции из конструктора базового класса не будет полиморфным. В момент вызова конструктора базового класса объект производного класса еще не создан, поэтому будет вызвана версия функции базового класса. Аналогично и для деструктора — после вызова деструктора производного класса объект "понижается" до базового.

#include <iostream>

class Base {
public:
    Base() { print(); }  // Вызов виртуальной функции в конструкторе
    virtual void print() { std::cout << "Base\n"; }
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    Derived() { print(); }
    void print() override { std::cout << "Derived\n"; }
};

int main() {
    Derived d;
    // Вывод: Base (из конструктора Base)
    //        Derived (из конструктора Derived)
    return 0;
}

2. Умные указатели (Smart Pointers)

Без этой темы не обходится ни одно современное собеседование. Умные указатели — основа RAII и управления ресурсами в C++.

Вопрос: В чем разница между std::unique_ptr, std::shared_ptr и std::weak_ptr? Когда что использовать?

Ответ:

  • unique_ptr — единоличное владение. Не копируется, только перемещается. Идеален для ресурсов, которые должны быть освобождены при выходе из области видимости.
  • shared_ptr — разделяемое владение. Использует счетчик ссылок. Удаляет объект, когда счетчик становится равен нулю.
  • weak_ptr — "наблюдатель" для shared_ptr. Не увеличивает счетчик ссылок. Используется для предотвращения циклических зависимостей.
#include <memory>
#include <iostream>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource released\n"; }
};

int main() {
    // unique_ptr
    std::unique_ptr<Resource> uptr = std::make_unique<Resource>();
    
    // shared_ptr
    std::shared_ptr<Resource> sptr1 = std::make_shared<Resource>();
    std::shared_ptr<Resource> sptr2 = sptr1;  // Счетчик ссылок = 2
    
    // weak_ptr
    std::weak_ptr<Resource> wptr = sptr1;  // Не увеличивает счетчик
    
    if (auto locked = wptr.lock()) {
        std::cout << "Resource is still alive\n";
    }
    
    return 0;
}

3. Move-семантика и rvalue-ссылки

Move-семантика — одна из ключевых фич C++11. На собеседовании вас могут попросить написать move-конструктор или объяснить, что такое forwarding reference.

Вопрос: Что такое move-семантика и зачем она нужна? Приведите пример, когда она ускоряет код.

Ответ: Move-семантика позволяет передавать владение ресурсами (например, динамической памятью) без их копирования. Вместо глубокого копирования мы "перемещаем" указатели, обнуляя исходный объект. Это критически важно для производительности при работе с большими контейнерами.

#include <vector>
#include <string>
#include <iostream>

class MyString {
private:
    char* data;
    size_t size;
public:
    // Обычный конструктор
    MyString(const char* str) : size(strlen(str)) {
        data = new char[size + 1];
        memcpy(data, str, size + 1);
    }
    
    // Move-конструктор
    MyString(MyString&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    
    ~MyString() { delete[] data; }
};

int main() {
    std::vector<MyString> vec;
    vec.push_back(MyString("Hello"));  // Вызов move-конструктора
    // Без move-семантики было бы глубокое копирование
    return 0;
}

4. RAII и исключения

RAII (Resource Acquisition Is Initialization) — это не просто паттерн, это философия C++. Интервьюеры проверяют, понимаете ли вы, как RAII связан с безопасностью исключений.

Вопрос: Как RAII помогает писать исключение-безопасный код? Приведите пример.

Ответ: RAII гарантирует, что ресурс будет освобожден при любом выходе из области видимости — будь то нормальное завершение или исключение. Деструкторы вызываются при раскрутке стека, поэтому если вы обернули мьютекс в RAII-обертку, он гарантированно разблокируется.

#include <mutex>
#include <stdexcept>
#include <iostream>

std::mutex mtx;

void unsafe_function() {
    mtx.lock();
    // Если здесь вылетит исключение — мьютекс останется заблокированным!
    throw std::runtime_error("Error");
    mtx.unlock();
}

void safe_function() {
    std::lock_guard<std::mutex> lock(mtx);  // RAII-обертка
    // Исключение безопасно — деструктор lock_guard разблокирует мьютекс
    throw std::runtime_error("Error");
}

int main() {
    try {
        safe_function();
    } catch (...) {
        std::cout << "Exception caught, mutex is unlocked\n";
    }
    return 0;
}

5. Шаблоны и метапрограммирование

Шаблоны — одна из самых мощных (и сложных) частей C++. На собеседовании могут спросить про специализацию шаб

Блоги

Рекомендаций по книгам