10 ошибок новичков в C++: как не сломать код

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

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

Начать курс

Почему C++ ломает даже бывалых, а новичков — тем более

C++ — язык безжалостный. Он даёт вам полный контроль над памятью, но и требует абсолютной дисциплины. Если вы только начинаете изучать C++, то наверняка уже столкнулись с тем, что программа компилируется, но вылетает с ошибкой, или (что ещё хуже) работает неправильно без всяких ошибок. Это не ваша вина — это природа языка. Но мы можем помочь вам избежать самых частых граблей, на которые наступают все новички.

В этой статье разберём 10 критических ошибок при изучении C++, которые превращают код в адскую смесь из утечек памяти и неопределённого поведения. Поехали!

1. Забыли удалить память: классическая утечка

Самая популярная ошибка новичков в C++ — выделение динамической памяти через new и забытый delete. Программа жрёт оперативку, как пылесос, а вы даже не замечаете.

Плохой пример:

int* ptr = new int(42);
// ... много кода ...
// Забыли: delete ptr;

Результат: утечка памяти. Программа работает, но с каждым вызовом потребляет всё больше ресурсов. В современных проектах это недопустимо.

Как исправить:

  • Используйте умные указатели (std::unique_ptr, std::shared_ptr) — они автоматически очищают память.
  • Если без new/delete никак — всегда ставьте delete сразу после последнего использования.
#include <memory>
std::unique_ptr<int> ptr = std::make_unique<int>(42);
// Память освободится автоматически при выходе из области видимости

2. Использование using namespace std; в глобальной области

Эта строка — как бомба замедленного действия. На небольших учебных примерах она работает, но в реальном проекте приводит к конфликтам имён.

Проблема:

#include <iostream>
#include <vector>
using namespace std;

// Через год в проекте появляется свой класс vector
class vector { ... }; // Ошибка компиляции!

Имена из std (например, vector, cout) становятся глобальными, и любой ваш класс с таким же именем вызовет коллизию.

Лучше так:

  • Пишите std::cout, std::vector явно.
  • Или используйте using std::cout; только для конкретных имён.

3. Неопределённое поведение: выстрел в ногу

Самая коварная ошибка при изучении C++ — это неопределённое поведение (UB — Undefined Behavior). Компилятор не выдаст ошибку, программа может работать, а может стереть ваш жёсткий диск. Шутка, но близко к правде.

Примеры UB:

  • Выход за границы массива: int arr[5]; arr[10] = 42;
  • Разыменование нулевого указателя: int* p = nullptr; *p = 5;
  • Использование переменной до инициализации.
int x; // не инициализирована
std::cout << x; // UB: может вывести мусор, 0 или вызвать краш

Совет: всегда инициализируйте переменные. Используйте std::array или std::vector вместо сырых массивов — они проверяют границы (в debug-режиме).

4. Путаница между указателем и ссылкой

Новички часто не понимают разницу между int* p и int& ref. Ошибка: пытаются перепривязать ссылку, или забывают разыменовать указатель.

Типичная ошибка:

void change(int* p) {
    p = new int(10); // меняем локальную копию указателя, оригинал не изменился
}

int main() {
    int* ptr = nullptr;
    change(ptr);
    std::cout << *ptr; // UB: ptr всё ещё nullptr
}

Как правильно: передавайте указатель по ссылке (int*& p) или используйте ссылки, если не нужно менять адрес.

5. Игнорирование std::vector в пользу сырых массивов

В C++ есть замечательный контейнер std::vector, но новички упорно пишут int arr[100]. В 2024 году это моветон.

Почему std::vector лучше:

  • Динамический размер — не нужно гадать, сколько элементов понадобится.
  • Автоматическое управление памятью.
  • Методы .size(), .push_back(), .at() с проверкой границ.
std::vector<int> vec = {1, 2, 3};
vec.push_back(4);
std::cout << vec.size(); // 4

Изучение C++ без std::vector — как готовка без ножа: можно, но больно.

6. Копирование вместо передачи по ссылке

Когда вы передаёте объект в функцию по значению, создаётся его полная копия. Для больших структур это катастрофа производительности.

Ошибка:

void process(std::string s) { // копия всей строки
    // ...
}

int main() {
    std::string huge = "..." // 1 млн символов
    process(huge); // копирование 1 МБ данных
}

Исправление: передавайте по константной ссылке (const std::string&), если не планируете менять объект.

7. Забыли про virtual деструктор в базовом классе

Классика полиморфизма: если вы удаляете объект производного класса через указатель на базовый, без виртуального деструктора — вызывается только деструктор базового класса. Ресурсы дочернего класса утекают.

class Base {
public:
    ~Base() { std::cout << "Base dtor\n"; } // не virtual!
};

class Derived : public Base {
    int* data = new int[100];
public:
    ~Derived() { delete[] data; std::cout << "Derived dtor\n"; }
};

int main() {
    Base* b = new Derived();
    delete b; // Вызовется только ~Base(), память data не освобождена!
}

Правило: если класс имеет хотя бы одну виртуальную функцию, деструктор должен быть виртуальным.

8. Неправильная работа с const

Новички или забывают ста

Блоги

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