Підводні камені у порядку зменшення їх важливості
Перш за все, вам слід відвідати нагородженого запитаннями про C ++ . У ньому є багато хороших відповідей на підводні камені. Якщо у вас виникли якісь запитання, відвідайте ##c++
на irc.freenode.org
в IRC . Ми раді допомогти вам, якщо зможемо. Зверніть увагу, що всі наступні підводні камені спочатку написані. Вони не просто копіюються із випадкових джерел.
delete[]
на new
, delete
наnew[]
Рішення : Виконання вищезазначеного поступається невизначеній поведінці: Все може статися. Зрозумійте свій код і те, що він робить, і завжди delete[]
те, що ви new[]
, і delete
що ви new
, тоді цього не станеться.
Виняток :
typedef T type[N]; T * pT = new type; delete[] pT;
Тобі потрібно delete[]
хоча ви new
, оскільки ви створили масив. Тож якщо ви працюєте з ними typedef
, будьте особливо обережні.
Виклик віртуальної функції в конструкторі або деструкторі
Рішення : Виклик віртуальної функції не призведе до перевизначення функцій у похідних класах. Виклик а чисто віртуальної функції в конструкторі чи деструкторі є невизначеною поведінкою.
Дзвінок delete
або delete[]
на вже видаленому покажчику
Рішення : Призначте 0 кожному видаленому покажчику. Виклик delete
або delete[]
нульовий покажчик нічого не робить.
Беручи розмір покажчика, коли потрібно обчислити кількість елементів "масиву".
Рішення : Передайте кількість елементів поряд з покажчиком, коли вам потрібно передати масив як покажчик у функцію. Використовуйте запропоновану функцію тут якщо ви берете розмір масиву, який повинен бути справді масивом.
Використання масиву, ніби це вказівник. Таким чином, використовуючиT **
для двовимірного масиву.
Рішення : Дивіться тут чому вони різні і як з ними поводитися.
Запис у рядковий літерал: char * c = "hello"; *c = 'B';
Рішення : Виділіть масив, який ініціалізується з даних рядкового літералу, тоді ви можете написати в нього:
char c[] = "hello"; *c = 'B';
Запис у рядковий літерал є невизначеною поведінкою. У будь-якому випадку, наведене вище перетворення з рядкового літералу вchar *
застаріле. Тож компілятори, ймовірно, попереджатимуть, якщо ви збільшите рівень попередження.
Створення ресурсів, а потім забування звільнити їх, коли щось кине.
Рішення : Використовуйте розумні вказівники, як std::unique_ptr
або std::shared_ptr
як вказано в інших відповідях.
Змінення об’єкта двічі, як у цьому прикладі: i = ++i;
Рішення : Вищезазначене повинно було присвоїти i
значення i+1
. Але чим він займається, не визначено. Замість того щоб збільшувати i
та призначати результат, він змінюється і i
з правого боку. Зміна об'єкта між двома точками послідовності є невизначеною поведінкою. Точки послідовності включають ||
, &&
, comma-operator
, semicolon
і entering a function
(не вичерпний список!). Змініть код на такий, щоб він поводився правильно:i = i + 1;
Різні питання
Забувши змити потоки перед викликом функції блокування, наприклад sleep
.
Рішення : Промийте потік потоковою передачею std::endl
замість \n
або за допомогою дзвінкаstream.flush();
.
Оголошення функції замість змінної.
Рішення : Проблема виникає, оскільки компілятор, наприклад, інтерпретує
Type t(other_type(value))
як оголошення функції функції, яка t
повертається Type
і має параметр типу, other_type
який викликається value
. Ви вирішуєте це, ставлячи дужки навколо першого аргументу. Тепер ви отримуєте змінну t
типу Type
:
Type t((other_type(value)))
Виклик функції вільного об'єкта, який оголошений лише в поточній одиниці перекладу ( .cpp
файлі).
Рішення : Стандарт не визначає порядок створення вільних об’єктів (у просторі імен), визначений для різних одиниць перекладу. Виклик функції-члена на ще не побудованому об'єкті є невизначеною поведінкою. Ви можете визначити таку функцію в одиниці перекладу об’єкта та викликати її з інших:
House & getTheHouse() { static House h; return h; }
Це створило б об’єкт на вимогу і залишило б вам повністю побудований об’єкт під час виклику на ньому функцій.
Визначення шаблону у .cpp
файлі, тоді як він використовується в іншому .cpp
файлі.
Рішення : Майже завжди ви отримуватимете такі помилки undefined reference to ...
. Помістіть усі визначення шаблонів у заголовок, щоб, коли компілятор їх використовує, він уже міг створити необхідний код.
static_cast<Derived*>(base);
якщо base - вказівник на віртуальний базовий клас Derived
.
Рішення : Віртуальний базовий клас - це база, яка виникає лише один раз, навіть якщо вона неодноразово успадковується різними класами в дереві успадкування. Виконання вищезазначеного не дозволяється стандартом. Для цього використовуйте dynamic_cast і переконайтесь, що ваш базовий клас є поліморфним.
dynamic_cast<Derived*>(ptr_to_base);
якщо основа неполіморфна
Рішення : Стандарт не допускає зниження покажчика або посилання, коли переданий об’єкт не є поліморфним. Він або один з його базових класів повинен мати віртуальну функцію.
Прийняття вашої функції T const **
Рішення : Ви можете подумати, що це безпечніше, ніж використання T **
, але насправді це спричинить головний біль у людей, які хочуть пройтиT**
: Стандарт не дозволяє цього. Це дає чіткий приклад, чому це заборонено:
int main() {
char const c = ’c’;
char* pc;
char const** pcc = &pc;
*pcc = &c;
*pc = ’C’;
}
Завжди приймайте T const* const*;
замість цього.
Ще однією (закритою) пасткою про C ++, тому люди, які їх шукають, знайдуть їх - це підводні камені в C ++ .