<
  • Главная
Статьи

Антипаттерн програмування на плюсах

С ++ - справжній чоловічий мову програмування. Програмуючи на ньому, ти легко можеш відстрелити собі ногу, та так, що дізнаєшся про це тільки через добрі півроку. Що ж робити? Читати стандарт! І цю статтю, до речі, теж.

Коли пишеш код на C ++, завжди треба думати, як він буде використовуватися. Від цього залежить, які можливості мови треба застосувати, щоб цей код працював саме так, як задумано. Сі з плюсами рясніє різноманітними ключовими словами, які ніби як не обов'язкові до використання, і багато програмісти їх ігнорують. Ще С ++ дуже любить, щоб кодер пам'ятав і дбав про деталі, без належного рівня уважності баги будуть сипатися один за іншим. Для більшої переконливості наведемо кілька приклад.

Всі знають, що таке в C ++ конструктор і для чого він потрібен. Якщо ініціалізація будь-якого члена в класі лягає на плечі користувача цього класу, то таку операцію можна провести через передачу в конструктор якогось параметра. Давай подивимося на такий клас:

class MyClass {public: MyClass (): m_x (0), m_y (0) {} MyClass (int x): m_x (x), m_y (0) {} void SetY (int y) {m_y = y; } Private: int m_x; int m_y; };

Все просто до неподобства. Є конструктор за замовчуванням і конструктор, не започатковано змінну член m_x значенням x. Ще є m_y, для ініціалізації якого служить функція член SetY. Цей клас можна використовувати наступним чином:

MyClass myObject (7); myObject.SetY (9); // ... myObject = 12;

Здавалося б, код абсолютно нешкідливий. Спочатку ми створюємо об'єкт myObject, ініціалізувавши m_x значенням 7. Після цього присвоюємо m_y значення 9. Через кілька рядків коду ми робимо myObject = 12 ;, і в цьому полягає велика проблема. Так як для класу є конструктор, який приймає як параметр значення типу int, то під час присвоєння компілятор, вирішивши полегшити нам життя, автоматично перетворює 12 в об'єкт типу MyClass і замінить їм myObject. При цьому значення в m_y буде безповоротно втрачено. Якщо програміст не розуміє, що він робить, або просто припустився такої помилки через неуважність, то це загрожує нам великими проблемами. У деяких випадках проблеми можуть стати особливо серйозними. Для многопоточного програмування був придуманий патерн Monitor, який дозволяє захистити будь-який об'єкт м'ютексів, щоб згодом надавати до нього безпечний доступ в багатопотокової середовищі. Код цього патерну схожий на код класу з попереднього прикладу, різниця лише в тому, що замість члена int m_y буде std :: mutex m_mutex, а функція SetYбудет замінена на AccessToX. Для того щоб безпечно працювати з захищеними даними, монітор дозволяє передати в перевантажений operator () лямбда-функцію, яка буде виконуватися під м'ютексів. Тепер уявімо, що буде, якщо в багатопотокової середовищі ми разом з компілятором проведемо такий трюк з неявним перетворенням і подальшим присвоєнням монітора новоствореного тимчасового об'єкта.

Тепер уявімо, що буде, якщо в багатопотокової середовищі ми разом з компілятором проведемо такий трюк з неявним перетворенням і подальшим присвоєнням монітора новоствореного тимчасового об'єкта

Статистичний аналіз коду в Visual Studio

У більшості випадків все відпрацює, здавалося б, як треба, але якщо такий код буде викликаний одночасно з різних потоків, то ми отримаємо всі принади data race. Роблячи myObject = 12 для об'єкта монітора, програміст, скоріше, хотів перевизначити захищається значення, але з якихось причин не скористався спеціально створеним для цього механізмом. Можна, звичайно, сказати йому: «Геть з професії», але насправді винен саме той, хто проектував клас монітора. Щоб подібних проблем не виникало, достатньо лише оголосити конструктор MyClass (int x) з ключовим словом explicit: воно відключить неявне перетворення аргументу в об'єкт, і конструкція myObject = 12 Не скомпілюється, що змусить неуважного кодера задуматися.

Поліморфізм в C ++ використовується по повній. Всі програмісти, коли-небудь стикаються з ООП, знають, що це таке. Дуже зручно, наприклад, зберігати об'єкти різних пов'язаних між собою класів в контейнері, який працює з покажчиками на базовий клас. Завдяки віртуальним функціям виконується саме той код, який нам потрібен. Це надзвичайно зручно і звично, і деякі навіть забувають про те, що доступ до функцій спадкоємця можна отримати тільки через покажчик, але ніяк не через об'єкт базового класу. Щоб було зрозуміліше, поглянемо на наступний код:

class A {public: A (): m_a (0) {std :: cout << "Construct A" << std :: endl; } A (const A & other) {std :: cout << "Copy construct A" << std :: endl; CopyFrom (other); } Virtual ~ A () {} virtual void Foo () {std :: cout << "A :: Foo" << std :: endl; } Private: void CopyFrom (const A & other) {if (this! = & Other) {m_a = other.m_a; }} Private: int m_a; }; class B: public A {public: B (): A () m_b (0) {std :: cout << "Construct B" << std :: endl; } B (const B & other) {std :: cout << "Copy construct B" << std :: endl; A :: CopyFrom (other); CopyFrom (other); } Void Foo () override {std :: cout << "B :: Foo" << std :: endl; } Private: void CopyFrom (const B & other) {if (this! = & Other) {m_b = other.m_b; }} Private: int m_b; };

У нас є базовий клас A і його спадкоємець B. Віртуальна функція Foo перевизначена в дочірньому класі. До того ж ми явно визначили конструктор копіювання в обох класах, але це ми залишимо на потім. Давай уявимо, ніби у нас є вектор, в якому потрібно зберігати об'єкти обох цих типів, але з якихось причин ми вирішили, що в контейнері повинні лежати самі об'єкти, а не покажчики на них. Запущено туди кілька об'єктів, ми проходимо по контейнеру і викликаємо для кожного Foo.

std :: vector <A> myVector; myVector.push_back (B ()); myVector.push_back (B ()); myVector.push_back (A ()); for (auto & object: myVector) {object.Foo (); }

Після виконання циклу ми очікуємо побачити в консольному виведення рядка, що свідчать про виклик двох B :: Foo і однієї A :: Foo, але попри це все три виклики будуть виводити в output A :: Foo. Виходить, поліморфізм не працює? Не зовсім так.

При приміщенні об'єктів в вектор відбувається їх копіювання. Ми спеціально переопределили конструктор копіювання, щоб зрозуміти, що відбувається. А відбувається те, що викликається конструктор копіювання тільки для класу A, так як саме він є шаблонною параметром контейнера. Все, що стосується класу B, при цьому не копіюється, в тому числі і таблиця покажчиків на віртуальні функції. Таким чином, у векторі лежать звичайні об'єкти класу A. Така помилка можлива як через незнання основ, так і по простій неуважності. Найнебезпечніше в цій ситуації полягає в тому, що розплата може прийти через багато днів і шукати причину глюків буде складно.

Досить часто програмісти стикаються із завданням виклику клієнтського коду зі свого. Це можуть бути прості callbacks або реалізація сигналів / слотів. Що не мають досвіду в подібних речах програмісти наївно сподіваються, що їх класами будуть користуватися ідеальні кодери, які не припускаються помилок. Творіння подібних программеров виглядає приблизно так:

void SomeClass :: Foo (std :: function <void ()> callback) {m_mutex.lock () // Робимо що-небудь добре callback (); m_mutex.unlock (); }

Для більшої драматичності ми спеціально додали м'ютекс в наш код. Відчув весь трагізм того, що відбувається? Справа в тому, що при виклику callback () цілком може статися виняток, яке передчасно призведе до завершення функції SomeClass :: Foo, і значить, м'ютекс, який ми заблокували на початку цієї функції, залишиться в такому стані назавжди, що згодом неминуче призведе до Дедлок.

Справа в тому, що при виклику callback () цілком може статися виняток, яке передчасно призведе до завершення функції SomeClass :: Foo, і значить, м'ютекс, який ми заблокували на початку цієї функції, залишиться в такому стані назавжди, що згодом неминуче призведе до Дедлок

Шматочок коду сигналів / слотів

Виправити становище справ досить просто - достатньо лише використовувати керуючий об'єкт для нашого мьютекса, в даному випадку цілком підійде std :: lock_guard. Стандарт C ++ гарантує, що локальні об'єкти в разі спрацювання exception будуть знищені, а отже, будуть викликані їх деструктори, деструктор lock_guard, в свою чергу, розблокує м'ютекс. Можливий і інший варіант розвитку подій. Припустимо, наш коллбек потрібно викликати не один раз, а кілька разів в циклі. В цьому випадку ми цілком можемо розраховувати на дострокове завершення нашого коду через спрацювання все того ж виключення в callback. На щастя, і тут нескладно впоратися з випали на нашу долю труднощами.

void SomeClass :: Foo (std :: function <void ()> callback) {while (/ * поки я живий * /) {// Працюємо на благо людства try {callback (); } Catch (...) {// Обробляємо виключення тут! }}}

Обернувши виклик користувальницького коду в try-catch блок, ми зможемо без побоювань молотити біти і байти в нашому циклі, не боячись, що який-небудь кодер-самоучка захоче поділити все це на нуль. З мого великого і досить-таки очевидного розповіді треба засвоїти дві речі: завжди використовуй керуючі (RIIA) об'єкти для всіх ресурсів, які вимагають деініціалізацію, і відловлюють виключення при викликах клієнтського коду, якщо не хочеш неприємних сюрпризів.

Відстрелити собі ногу, займаючись кодінгом на плюсах, ще простіше, ніж проколупнути дірку в паперовій стіні японського будинку. Слід пам'ятати безліч нюансів і проявляти особливу пильність. Щоб не робити прикрих помилок, потрібно багато досвіду. А для профілактики можна періодично перегортати стандарт C ++.

Що ж робити?
Виходить, поліморфізм не працює?
Відчув весь трагізм того, що відбувається?


Новости
    Без плагина
    На сайте WordPress имеется файл, именуемый как .htaccess. Многие пользователи не предают ему особого внимания и не используют все его возможности. На самом деле файл .htaccess – это дополнительные конфигурации

    Плагин подписки wordpress
    Очень трудно найти один плагин подписки wordpress , который объединил бы в себе все виды подписок, которые так необходимы сайту. Именно поэтому я решил сделать подборку лучших плагинов, которые смогут

    Слайд-шоу с помощью плагина для WordPress UnPointZero Slider
    Плагин для cms WordPress UnPointZero Slider – новостной слайдер. Он отражает в форме слайд-шоу изображения со ссылками на ваши статьи и краткие выдержки оттуда. Его можно установить и на новостной сайт,

    Плагины для Wordpress
    С помощью этого плагина вы легко сможете интегрировать Google диск на ваш WordPress сайт или блог . Gravity Forms — лучший плагин для создания форм на WordPress, от самых простых (например, форма

    Подписки плагином JetPack: размещение и редакция формы подписки
    Вступление Здравствуйте! В этой статье я покажу, как использовать плагин JetPack для создания пользовательской формы подписки и как эту формы подписки плагином JetPack добавлять в статьи сайта, а при

    Чистка сайта WordPress плагином WP-optimize
    От автора Со временем использования система WordPress накапливает не нужные файлы, комментарии и неиспользуемые данные в базе данных. Эти файлы и данные создаются в процессе работы и нужны для этого,

    Возможности Jetpack плагина
    Вступление Возможности Jetpack плагина это более 30 функциональных модуля плагина, делающего его универсальным плагином WordPress, заменяющего аналогичные сторонние плагины. Jetpack один заменяет десятки

    Резервное копирование WordPress сайта без плагинов
    Вступление Резервное копирование WordPress это второе, что нужно научиться делать после установки WordPress. Можно сколько угодно говорить о безопасности сайта и его защите, но лучшего варианта защиты

    Плагины на приват для Майнкрафт ПЕ
    > > Плагины на приват для Майнкрафт ПЕ Порой всем нам хочется попробовать себя в роли администратора сервера и испытать эту ответственность, но, к сожалению, вы не всегда все знаете о создании

    Плагин WordPress Database Backup. Архивация базы данных блога на WordPress
    Привет друзья! Сегодня на очереди еще один простой, НО, необходимый и полезный плагин — плагин WordPress Database Backup , который с легкостью и самостоятельно произведет процесс, который научно называется:

  • Виртуальный хостинг

    Виртуальный хостинг. Возможности сервера распределяются в равной мере между всеми... 
    Читать полностью

  • Редизайн сайта

    Редизайн сайта – это полное либо частичное обновление дизайна существующего сайта.... 
    Читать полностью

  • Консалтинг, услуги контент-менеджера

    Сопровождение любых интернет ресурсов;- Знание HTML и CSS- Поиск и обновление контента;-... 
    Читать полностью

  • Трафик из соцсетей

    Сравнительно дешевый способ по сравнению с поисковым и контекстным видами раскрутки... 
    Читать полностью

  • Поисковая оптимизация

    Поисковая оптимизация (англ. search engine optimization, SEO) — поднятие позиций сайта в результатах... 
    Читать полностью