Підводні камені у порядку зменшення їх важливості
Перш за все, вам слід відвідати нагородженого запитаннями про 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 ++ .