portaldacalheta.pt
  • Основен
  • Подвижен
  • Дизайн На Марката
  • Възходът На Дистанционното
  • Жизнен Цикъл На Продукта
Back-End

Топ 10 на най-често срещаните грешки в C ++, които допускат разработчиците



Има много клопки, които a Разработчик на C ++ може да срещне. Това може да направи качественото програмиране много трудно и поддръжката много скъпа. Изучаването на синтаксиса на езика и наличието на добри умения за програмиране на подобни езици, като C # и Java, просто не е достатъчно, за да се използва пълният потенциал на C ++. Изискват години опит и голяма дисциплина, за да се избегнат грешки в C ++. В тази статия ще разгледаме някои от често срещаните грешки, които допускат разработчиците от всички нива, ако не са достатъчно внимателни при разработването на C ++.

Често срещана грешка # 1: Използване на двойки „new” и „delete” неправилно

Колкото и да се опитваме, е много трудно да освободим цялата динамично разпределена памет. Дори и да можем да го направим, често не е в безопасност от изключения. Нека разгледаме един прост пример:



void SomeMethod() { ClassA *a = new ClassA; SomeOtherMethod(); // it can throw an exception delete a; }

Ако бъде хвърлено изключение, обектът „a“ никога не се изтрива. Следващият пример показва по-безопасен и кратък начин за това. Той използва auto_ptr, който е остарял в C ++ 11, но старият стандарт все още се използва широко. Той може да бъде заменен с C ++ 11 unique_ptr или scoped_ptr от Boost, ако е възможно.



void SomeMethod() { std::auto_ptr a(new ClassA); // deprecated, please check the text SomeOtherMethod(); // it can throw an exception }

Без значение какво се случва, след създаването на обект “a” той ще бъде изтрит веднага щом изпълнението на програмата излезе от обхвата.



Това обаче беше само най-простият пример за този проблем на C ++. Има много примери, когато изтриването трябва да се извърши на друго място, може би във външна функция или друга нишка. Ето защо използването на new / delete по двойки трябва да се избягва напълно и вместо това да се използват подходящи интелигентни указатели.

Често срещана грешка # 2: Забравен виртуален деструктор

Това е една от най-често срещаните грешки, която води до изтичане на памет в производни класове, ако в тях има разпределена динамична памет. Има някои случаи, когато виртуалният деструктор не е желателен, т.е. когато клас не е предназначен за наследяване и неговият размер и производителност е от решаващо значение. Виртуалният деструктор или друга виртуална функция въвежда допълнителни данни в структурата на класа, т.е.посока към виртуална таблица, която увеличава размера на всеки екземпляр на класа.



Въпреки това, в повечето случаи класовете могат да бъдат наследени, дори ако първоначално не са предназначени. Така че е много добра практика да добавяте виртуален деструктор, когато е деклариран клас. В противен случай, ако клас не трябва да съдържа виртуални функции поради съображения за производителност, е добра практика да поставите коментар във файла за декларация на класа, указващ, че класът не трябва да се наследява. Един от най-добрите варианти за избягване на този проблем е да се използва IDE, който поддържа създаването на виртуален деструктор по време на създаването на клас.

Допълнителна точка към предмета са класовете / шаблоните от стандартната библиотека. Те не са предназначени за наследяване и нямат виртуален деструктор. Ако например създадем нов подобрен клас низ, който наследява публично от std :: string, има вероятност някой да го използва неправилно с указател или препратка към std :: string и да причини изтичане на памет.



class MyString : public std::string { ~MyString() { // ... } }; int main() { std::string *s = new MyString(); delete s; // May not invoke the destructor defined in MyString }

За да се избегнат подобни проблеми със C ++, по-безопасен начин за повторно използване на клас / шаблон от стандартната библиотека е използването на частно наследство или композиция.

Честа грешка # 3: Изтриване на масив с „изтриване“ или използване на интелигентен указател

Често срещана грешка # 3

mysql конвертирате в utf 8

Създаването на временни масиви с динамичен размер често е необходимо. След като вече не са необходими, е важно да освободите разпределената памет. Големият проблем тук е, че C ++ изисква специален оператор за изтриване със скоби [], който се забравя много лесно. Операторът delete [] не просто ще изтрие паметта, разпределена за масив, но първо ще извика деструктори на всички обекти от масив. Също така е неправилно да се използва операторът за изтриване без [] скоби за примитивни типове, въпреки че за тези типове няма деструктор. Няма гаранция за всеки компилатор, че указателят към масив ще сочи към първия елемент на масива, така че използването на delete без [] скоби може да доведе и до недефинирано поведение.

Използването на интелигентни указатели, като auto_ptr, unique_ptr, shared_ptr, с масиви също е неправилно. Когато такъв интелигентен указател излезе от обхват, той ще извика оператор за изтриване без [] скоби, което води до същите проблеми, описани по-горе. Ако за масив се изисква използване на интелигентен указател, възможно е да се използва scoped_array или shared_array от Boost или специализация unique_ptr.

Ако не се изисква функционалност на преброяване на референции, което е най-вече случаят с масиви, най-елегантният начин е да се използват STL вектори вместо това. Те не само се грижат за освобождаването на паметта, но предлагат и допълнителни функционалности.

Често срещана грешка # 4: Връщане на локален обект чрез препратка

Това е предимно грешка за начинаещи, но си струва да се спомене, тъй като има много наследствен код, който страда от този проблем. Нека разгледаме следния код, където програмист иска да направи някакъв вид оптимизация, като избягва ненужното копиране:

какво е маркер erc 20
Complex& SumComplex(const Complex& a, const Complex& b) { Complex result; ….. return result; } Complex& sum = SumComplex(a, b);

Обектът „сума“ сега ще сочи към локалния обект „резултат“. Но къде се намира обектът „резултат“, разположен след изпълнението на функцията SumComplex? Никъде. Той се намираше в стека, но след връщането на функцията стекът беше разгънат и всички локални обекти от функцията бяха унищожени. Това в крайна сметка ще доведе до недефинирано поведение, дори за примитивни типове. За да се избегнат проблеми с производителността, понякога е възможно да се използва оптимизация на възвръщаемата стойност:

Complex SumComplex(const Complex& a, const Complex& b) { return Complex(a.real + b.real, a.imaginar + b.imaginar); } Complex sum = SumComplex(a, b);

За повечето от днешните компилатори, ако редът за връщане съдържа конструктор на обект, кодът ще бъде оптимизиран, за да се избегнат всички ненужни копия - конструкторът ще бъде изпълнен директно върху обекта „sum“.

Често срещана грешка # 5: Използване на препратка към изтрит ресурс

Тези проблеми на C ++ се случват по-често, отколкото си мислите, и обикновено се наблюдават в многонишкови приложения. Нека разгледаме следния код:

Тема 1:

Connection& connection= connections.GetConnection(connectionId); // ...

Тема 2:

connections.DeleteConnection(connectionId); // …

Тема 1:

connection.send(data);

В този пример, ако и двете нишки са използвали един и същ идентификатор на връзката, това ще доведе до недефинирано поведение. Грешките при нарушаване на достъпа често са много трудни за намиране.

В тези случаи, когато повече от една нишка имат достъп до един и същ ресурс, е много рисковано да се запазят указатели или препратки към ресурсите, тъй като друга нишка може да го изтрие. Много по-безопасно е да използвате интелигентни указатели с преброяване на референции, например shared_ptr от Boost. Той използва атомни операции за увеличаване / намаляване на референтен брояч, така че е безопасен за нишки.

Често срещана грешка # 6: Разрешаване на изключенията да оставят деструктори

Не е често необходимо да се хвърля изключение от деструктор. Дори и тогава има по-добър начин да направите това. Въпреки това, изключенията обикновено не се изхвърлят изрично от деструкторите. Може да се случи така, че проста команда за регистриране на унищожаване на обект да предизвика хвърляне на изключение. Нека разгледаме следния код:

как да имплементирате bootstrap в html
class A { public: A(){} ~A() { writeToLog(); // could cause an exception to be thrown } }; // … try { A a1; A a2; } catch (std::exception& e) { std::cout << 'exception caught'; }

В горния код, ако изключение се случи два пъти, например по време на унищожаването на двата обекта, операторът catch никога не се изпълнява. Тъй като има две паралелни изключения паралелно, без значение дали са от един и същи тип или от различен тип, средата за изпълнение на C ++ не знае как да се справи с нея и извиква прекратяваща функция, която води до прекратяване на изпълнението на програма.

Така че основното правило е: никога не позволявайте на изключенията да оставят деструктори. Дори и да е грозно, потенциалното изключение трябва да бъде защитено по този начин:

try { writeToLog(); // could cause an exception to be thrown } catch (...) {}

Често срещана грешка # 7: Използване на „auto_ptr“ (неправилно)

Шаблонът auto_ptr е остарял от C ++ 11 поради редица причини. Той все още се използва широко, тъй като повечето проекти все още се разработват в C ++ 98. Той има определена характеристика, която вероятно не е позната на всички разработчици на C ++ и може да причини сериозни проблеми на някой, който не е внимателен. Копирането на обект auto_ptr ще прехвърли собственост от един обект на друг. Например следният код:

auto_ptr a(new ClassA); // deprecated, please check the text auto_ptr b = a; a->SomeMethod(); // will result in access violation error

... ще доведе до грешка при нарушаване на достъпа. Само обект 'b' ще съдържа указател към обекта от клас A, докато 'a' ще бъде празно. Опитът за достъп до член на класа на обекта „а“ ще доведе до грешка при нарушаване на достъпа. Има много начини за неправилно използване на auto_ptr. Четири много критични неща, които трябва да запомните за тях, са:

  1. Никога не използвайте auto_ptr вътре в STL контейнери. Копирането на контейнери ще остави източниците на контейнери с невалидни данни. Някои STL алгоритми също могат да доведат до обезсилване на “auto_ptr”.

  2. Никога не използвайте auto_ptr като аргумент на функция, тъй като това ще доведе до копиране и оставете стойността, предадена на аргумента, невалидна след извикването на функцията.

  3. Ако auto_ptr се използва за членове на данни от клас, не забравяйте да направите правилно копие в конструктор за копиране и оператор за присвояване или да забраните тези операции, като ги направите частни.

  4. Винаги когато е възможно използвайте някой друг съвременен интелигентен указател вместо auto_ptr.

Често срещана грешка # 8: Използване на анулирани итератори и референции

Би било възможно да се напише цяла книга по този въпрос. Всеки STL контейнер има някои специфични условия, при които обезсилва итератори и препратки. Важно е да знаете тези подробности, докато използвате каквато и да е операция. Подобно на предишния проблем на C ++, този също може да се появи много често в многонишкови среди, така че е необходимо да се използват механизми за синхронизация, за да се избегне. Нека видим следния последователен код като пример:

vector v; v.push_back(“string1”); string& s1 = v[0]; // assign a reference to the 1st element vector::iterator iter = v.begin(); // assign an iterator to the 1st element v.push_back(“string2”); cout << s1; // access to a reference of the 1st element cout << *iter; // access to an iterator of the 1st element

От логическа гледна точка кодът изглежда напълно добре. Въпреки това, добавянето на втория елемент към вектора може да доведе до преразпределение на паметта на вектора, което ще направи итератора и референцията невалидни и ще доведе до грешка при нарушаване на достъпа при опит за достъп до тях в последните 2 реда.

Често срещана грешка # 9: Предаване на обект по стойност

Често срещана грешка # 9

Вероятно знаете, че е лоша идея да предавате обекти по стойност поради въздействието му върху производителността. Мнозина го оставят така, за да избегнат въвеждането на допълнителни символи, или вероятно мислят да се върнат по-късно, за да направят оптимизацията. Обикновено никога не се прави и в резултат води до по-малко ефективни кодове и кодове, които са склонни към неочаквано поведение:

class A { public: virtual std::string GetName() const {return 'A';} … }; class B: public A { public: virtual std::string GetName() const {return 'B';} ... }; void func1(A a) { std::string name = a.GetName(); ... } B b; func1(b);

Този код ще се компилира. Извикването на функцията „func1“ ще създаде частично копие на обекта „b“, т.е. ще копира само част от клас „A“ на обекта „b“ в обекта „a“ („проблем с нарязването“). Така че вътре във функцията тя също ще извика метод от клас 'A' вместо метод от клас 'B', който най-вероятно не е това, което се очаква от някой, който извиква функцията.

хакване на кредитни карти в google play

Подобни проблеми възникват при опит за улавяне на изключения. Например:

class ExceptionA: public std::exception; class ExceptionB: public ExceptionA; try { func2(); // can throw an ExceptionB exception } catch (ExceptionA ex) { writeToLog(ex.GetDescription()); throw; }

Когато изключение от тип ExceptionB бъде хвърлено от функцията “func2”, то ще бъде уловено от блока catch, но поради проблема с нарязването ще бъде копирана само част от клас ExceptionA, ще бъде извикан неправилен метод и също повторно хвърляне ще хвърли неправилно изключение към външен блок try-catch.

За да обобщим, винаги предавайте обектите по препратка, а не по стойност.

Често срещана грешка # 10: Използване на дефинирани от потребителя конверсии от конструктор и оператори на преобразуване

Дори потребителските реализации понякога са много полезни, но могат да доведат до непредсказуеми реализации, които е много трудно да се намерят. Да приемем, че някой е създал библиотека, която има низ клас:

class String { public: String(int n); String(const char *s); …. }

Първият метод е предназначен за създаване на низ с дължина n, а вторият е предназначен за създаване на низ, съдържащ дадените символи. Но проблемът започва веднага щом имате нещо подобно:

String s1 = 123; String s2 = ‘abc’;

В горния пример s1 ще се превърне в низ с размер 123, а не в низ, който съдържа символите „123“. Вторият пример съдържа единични кавички вместо двойни кавички (което може да се случи случайно), което също ще доведе до извикване на първия конструктор и създаване на низ с много голям размер. Това са наистина прости примери и има много по-сложни случаи, които водят до объркване и непредвидени конверсии, които е много трудно да се намерят. Има 2 общи правила за това как да избегнете подобни проблеми:

  1. Дефинирайте конструктор с изрична ключова дума, за да забраните неявни преобразувания.

  2. Вместо да използвате оператори за преобразуване, използвайте явни методи за разговор. Това изисква малко повече въвеждане, но е много по-чисто за четене и може да помогне за избягване на непредсказуеми резултати.

Заключение

C ++ е мощен език. Всъщност много от приложенията, които използвате всеки ден на компютъра си и които са се влюбили, вероятно са създадени с помощта на C ++. Като език, C ++ дава a огромна гъвкавост на разработчика, чрез някои от най-сложните функции, наблюдавани в обектно-ориентираните програмни езици. Тези сложни функции или гъвкавости обаче често могат да се превърнат в причина за объркване и разочарование за много разработчици, ако не се използват отговорно. Надяваме се, че този списък ще ви помогне да разберете как някои от тези често срещани грешки влияят върху това, което можете да постигнете с C ++.

Свързани: Как да научим езиците C и C ++: Крайният списък

Как да интегрирате OAuth 2 във вашия Django / DRF Back-end, без да полудявате

Back-End

Как да интегрирате OAuth 2 във вашия Django / DRF Back-end, без да полудявате
Инструментариумът на GWT: Изграждане на мощни предни краища на JavaScript с помощта на Java

Инструментариумът на GWT: Изграждане на мощни предни краища на JavaScript с помощта на Java

Уеб Интерфейс

Популярни Публикации
Създавайте данни от случаен шум с генерални състезателни мрежи
Създавайте данни от случаен шум с генерални състезателни мрежи
Миналото все още присъства - преглед на вечния дизайн
Миналото все още присъства - преглед на вечния дизайн
Финансово бедствие в криза: Не можете да предскажете, можете да подготвите
Финансово бедствие в криза: Не можете да предскажете, можете да подготвите
Бруталистки уеб дизайн, минималистичен уеб дизайн и бъдещето на Web UX
Бруталистки уеб дизайн, минималистичен уеб дизайн и бъдещето на Web UX
Разширени съвети и хакове за презентация на PowerPoint
Разширени съвети и хакове за презентация на PowerPoint
 
Архитект отпред
Архитект отпред
Студената технологична война: все още тук и все още се използва
Студената технологична война: все още тук и все още се използва
Въведение в Apache Spark с примери и случаи на употреба
Въведение в Apache Spark с примери и случаи на употреба
Комодитизирани смартфони: Привеждане на 4G в развиващите се страни
Комодитизирани смартфони: Привеждане на 4G в развиващите се страни
Как да създам API за Secure Node.js GraphQL
Как да създам API за Secure Node.js GraphQL
Популярни Публикации
  • как да създадете сметкоплан
  • пълноценни храни, собственост на walmart
  • как да разберете дали една компания е s corp или c corp
  • тип файл txt карта cvv 2017
  • най-добри практики за проектиране на уеб услуги
  • s корпорация срещу c корпорация срещу партньорство
Категории
  • Подвижен
  • Дизайн На Марката
  • Възходът На Дистанционното
  • Жизнен Цикъл На Продукта
  • © 2022 | Всички Права Запазени

    portaldacalheta.pt