Как пройти собеседование по 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++. На собеседовании могут спросить про специализацию шаб