Почему 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
Новички или забывают ста