У цій статті розглянемо сучасні інструменти, за допомогою яких можна поліпшити старі проекти на С ++. Завдяки новітнім компіляторам і оптимізаторам можна перелопатити купу коду, не витрачаючи на це півжиття.
Вступ
Чи доводилося вам зустрічати жахливі рядки на кшталт цих?
float* pfloats = new float[10]; // no delete [] later! :)
int x = pfloats[0];
Помітити помилку неважко. Навіть базовий компілятор послужливо попередить про можливу втрату даних при конвертації float в int. Але що робити з більш складним кодом в працюючому проект?
Visual Studio 2019 має вбудований аналізатор коду, який буде давати досить корисні підказки. Розглянемо наступний приклад:
#include <iostream>
class SuspiciousType {
public:
SuspiciousType() { }
~SuspiciousType() { std::cout << "destructor!\n"; }
int compute(int z) { return x + y + z; }
int x;
int y;
};
int main() {
SuspiciousType st;
float* pfloats = new float[10]{ 100.5f };
int z = pfloats[0];
}
В VS 2019 ми можемо налаштувати правила проекту під свої потреби. Включити всі пункти або створити детальний профіль.
При включенні аналізатора відразу отримуємо ряд попереджень.
Спочатку для класу SuspiciousType:
cpptests.cpp(5): warning C26495: Variable 'SuspiciousType::x' is uninitialized. Always initialize a member variable (type.6).
cpptests.cpp(5): warning C26455: Default constructor may not throw. Declare it 'noexcept' (f.6).
cpptests.cpp(6): warning C26432: If you define or delete any default operation in the type 'class SuspiciousType', define or delete them all (c.21).
cpptests.cpp(6): warning C26447: The function is declared 'noexcept' but calls function 'operator<<<std::char_traits<char> >()' which may throw exceptions (f.6).
cpptests.cpp(8): warning C26440: Function 'SuspiciousType::compute' can be declared 'noexcept' (f.6).
А потім і в функції main:
cpptests.cpp(16): warning C26462: The value pointed to by 'pfloats' is assigned only once, mark it as a pointer to const (con.4).
cpptests.cpp(17): warning C26496: The variable 'z' is assigned only once, mark it as const (con.4).
cpptests.cpp(17): warning C26481: Don't use pointer arithmetic. Use span instead (bounds.1).
cpptests.cpp(16): warning C26409: Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11).
cpptests.cpp(16): warning C26400: Do not assign the result of an allocation or a function call with an owner<T> return value to a raw pointer, use owner<T> instead (i.11).
Як бачите, середовище розробки успішно виявило всі істотні недоліки. Більш того, якщо попередження пов'язане з недотриманням гайдлайнів - в кінці буде вказано конкретний пункт, який легко поправити, знайшовши його в офіційній документації.
Як бонус, Visual Studio тепер підкреслює зеленою хвилястою лінією елементи, які вважає застарілими або сумнівними. При кліці на код показуються подібні коментарі.
Якщо ви не використовуєте останню версію Visual Studio, то зверніть увагу на Clang Power Tools. Це розширення дає приблизно такий же набір функцій.
Але розбирати відверто поганий код це одне, а чи можна отримати користь з цих інструментів на реальному проекті?
Розглянемо проект побільше
У грудні 2019 го я відкрив свій старий проект з часів навчання. Це візуалізація алгоритмів сортування, написана в далеких 2005/2006 роках на старому С ++, Win32Api і OpenGL. Код можна подивитися в репозиторії .
Програма приймає за вхід масив значень і обробляє їх зі швидкістю близько 30 дій в секунду. Кожен елемент промальовується на діаграмі. Зеленим позначається елемент, до якого здійснюється звернення в даний момент, а блакитним - сортуєма в даний момент частина масиву. Демонстрацію на прикладі Quick sort ви бачите в гіфці вище.
Незважаючи на гарний зовнішній вигляд, всередині є ряд жахливих помилок, так що не лякайтеся, якщо вирішите заглянути в репозиторій.
Це досить цікавий досвід - вивчати свій код, написаний 15 років тому. Я вирішив переробити проект під VS 2019. Щоб згадати, як все працювало, я реалізував Quick sort, якого спочатку в програмі не було.
Повідомлення про проблеми
Коли я включив аналізатор коду на повну, то отримав 956 попереджень ... Цього варто було очікувати, давайте розглянемо їх докладніше.
Використання констант
Компілятор бачить змінні, що не міняють значення в процесі виконання, і автоматично пропонує використовувати константи.
Наприклад, для такого коду:
case cmYawPitchRoll: {
float r = cos(m_fPitch);
float x = r*sin(m_fYaw);
float y = sin(m_fPitch);
float z = -r*cos(m_fYaw);
m_vTarget = VECTOR3D(x, y, z);
m_vUp = VECTOR3D(sin(m_fRoll), cos(m_fRoll), 0.0f);
break;
}
Попередження виглядає так:
Warning C26496 The variable 'r' is assigned only once, mark it as const (con.4).
Це стосується і функцій:
// ang * M_PI / 180.0f
попередження:
inline float DegToRad(float a) { return a*0.01745329252f; };
// rads * 180.0f / M_PI
inline float RadToDeg(float a) { return a*57.29577951f; };
Warning C26497 The function 'DegToRad' could be marked constexpr if compile-time evaluation is desired (f.4).
Неініціалізовані змінні
На жаль, тоді в моєму коді це було поширеною помилкою. Наприклад, для CGLFont я забув про m_fSize.
CGLFont(): m_FontMode(fmNone), m_iList(0), m_iTexture(0) { }
І отримав таке попередження:
Warning C26495 Variable 'CGLFont::m_fSize' is uninitialized. Always initialise a member variable (type.6).
Надмірне використання покажчиків
У 2005 я не особливо багато знав про розумні покажчики і всюди використовував new і delete.
У сучасному C ++ ми повинні уникати такого підходу. VisualStudio дбайливо знайде місця, які потребують модернізації:
g_Algorithms[ABUBBLE_SORT] = new CBubbleSortAlgorithm();
g_Algorithms[ASHAKER_SORT] = new CShakerSortAlgorithm();
І попередження:
Warning C26409 Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11).
До того ж компілятор може знаходити проблеми з null pointer. Наприклад, в такому коді, я отримав повідомлення про неперевірений на null покажчик:
Render(CAVSystem *avSystem) {
ColorType ct;
avSystem->BeginDrawing(1.0, (int)m_vArray.size());
...
Warning C26429 Symbol 'avSystem' is never tested for nullness, it can be marked as not_null (f.23).
А далі мені треба вирішити, додати перевірку або позначити покажчик, як not_null.
Перехід на nullptr
В цьому немає нічого складного, замінити всі NULL з мого коду на nullptr з C ++ 11 - тривіальна задача. Clang-tidy навіть може зробити це автоматично.
Використання noexept
У C++ 11 ми отримали специфікатор noexept для оптимізації генеруючих бінарних файлів. Природно, в своєму старому коді я цього використовувати не міг і отримав купу попереджень.
Наприклад, для такого коду:
void SetTempoBPS(double fTempo) { m_fTempo = fTempo; }
void SetTempoBPM(double fTempo) { m_fTempo = fTempo/60.0; }
double GetTempoBPS() { return m_fTempo; }
double GetTempoBPM() { return m_fTempo*60.0; }
VisualStudio дала наступне попередження:
Warning C26440 Function 'CBeat::SetTempoBPS' can be declared 'noexcept' (f.6).
І так, гетери повинні бути константними ...
більше noexept
Іноді додаванням специфікатору проблему не вирішити. Доводиться розглядати варіант повного оновлення функції. Ось що я отримав:
Warning C26447 The function is declared 'noexcept' but calls function 'Destroy()' which may throw exceptions (f.6).
Для коду:
CGLApp::~CGLApp() {
Destroy();
}
Використання override
З override схожа історія. У 2005 у нас не було такого модифікатора, тому для інтерфейсу, що визначає три чисто віртуальні функції:
// in the interface
virtual void Init(CViData *viData) = 0;
virtual void Step() = 0;
virtual void Stop() = 0;
у мене не було можливість висловити це в похідному класі і доводилося писати так:
//in derived
void Init(CViData *viData);
void Step();
void Stop();
C ++ 11 дала нам можливість це змінити:
// in derived
void Init(CViData *viData) override;
void Step() override;
void Stop() override;
Правило нуля
За якоїсь містичної причини я зробив купу порожніх деструкторів, і компілятор це помітив:
Warning C26432 If you define or delete any default operation in the type 'class CCamera', define or delete them all (c.21).
Висновок
Повертатися до своїх давніх проектів досить весело, особливо якщо вам подобаються їхні ідеї. Цікаво як з роками змінюються інструменти та підходи до рішень. Сучасні компілятори і засоби аналізу можуть бути відмінними напарниками і робити непогане базове рев'ю коду. Іноді навіть вказувати на місця, де варто підтягнути теорію нового покоління.
Звичайно, ви можете оновити весь свій код, покладаючись тільки на свою «силу», знання і досвід, але ж можна використовувати сучасні інструменти і зберегти купу часу? Visual Studio тільки один з багатьох.
0 комментариев
Добавить комментарий