Welke C++-valkuilen moet ik vermijden?

Ik herinner me dat ik voor het eerst leerde over vectoren in de STL en na een tijdje wilde ik een vector van bools gebruiken voor een van mijn projecten. Nadat ik vreemd gedrag had gezien en wat onderzoek had gedaan, ontdekte ik dat een vector van bools is niet echt een vector van bools.

Zijn er nog andere veelvoorkomende valkuilen in C++?


Antwoord 1, autoriteit 100%

Een korte lijst kan zijn:

  • Vermijd geheugenlekken door gedeelde aanwijzers te gebruiken om geheugentoewijzing en opruiming te beheren
  • Gebruik het idioom Resource Acquisition Is Initialization(RAII)-idioom om het opschonen van bronnen te beheren, vooral in de aanwezigheid van uitzonderingen
  • Vermijd het aanroepen van virtuele functies in constructors
  • Gebruik waar mogelijk minimalistische coderingstechnieken – bijvoorbeeld variabelen alleen declareren als dat nodig is, variabelen in kaart brengen en waar mogelijk vroeg ontwerpen.
  • Begrijp de uitzonderingsbehandeling in uw code goed – zowel met betrekking tot uitzonderingen die u gooit, als met betrekking tot de uitzonderingen die worden gegenereerd door klassen die u mogelijk indirect gebruikt. Dit is vooral belangrijk in de aanwezigheid van sjablonen.

RAII, gedeelde pointers en minimalistische codering zijn natuurlijk niet specifiek voor C++, maar ze helpen problemen te voorkomen die vaak voorkomen bij het ontwikkelen in de taal.

Enkele uitstekende boeken over dit onderwerp zijn:

  • Effectieve C++ – Scott Meyers
  • Effectievere C++ – Scott Meyers
  • C++-coderingsnormen – Sutter & Alexandrescu
  • Veelgestelde vragen over C++ – Cline

Het lezen van deze boeken heeft me vooral geholpen om de valkuilen te vermijden waar je naar vraagt.


Antwoord 2, autoriteit 68%

Valkuilen in afnemende volgorde van belangrijkheid

Allereerst ga je naar de bekroonde C++ FAQ. Het heeft veel goede antwoorden op valkuilen. Als je nog vragen hebt, bezoek dan ##c++op irc.freenode.orgin IRC. We helpen je graag verder, als we kunnen. Merk op dat alle volgende valkuilen oorspronkelijk zijn geschreven. Ze worden niet zomaar gekopieerd uit willekeurige bronnen.


delete[]op new, deleteop new[]

Oplossing: het bovenstaande leidt tot ongedefinieerd gedrag: alles kan gebeuren. Begrijp je code en wat het doet, en delete[]altijd wat je new[], en deletewat je new, dan gebeurt dat niet.

Uitzondering:

typedef T type[N]; T * pT = new type; delete[] pT;

Je moet delete[]ook al ben je new, aangezien je een array nieuw hebt gemaakt. Wees dus extra voorzichtig als u met typedefwerkt.


Een virtuele functie aanroepen in een constructor of destructor

Oplossing: als u een virtuele functie aanroept, worden de overheersende functies in de afgeleide klassen niet aangeroepen. Het aanroepen van een pure virtuele functiein een constructor of destructor is ongedefinieerd gedrag.


Aanroepen van deleteof delete[]op een reeds verwijderde aanwijzer

Oplossing: wijs 0 toe aan elke aanwijzer die u verwijdert. Het aanroepen van deleteof delete[]op een null-pointer doet niets.


De grootte van een aanwijzer nemen, wanneer het aantal elementen van een ‘array’ moet worden berekend.

Oplossing: geef het aantal elementen naast de aanwijzer door wanneer u een array als aanwijzer in een functie moet doorgeven. Gebruik de functie hierals je de grootte van een array neemt die eigenlijk een array zou moeten zijn.


Een array gebruiken alsof het een aanwijzer is. Dus, gebruik T **voor een tweedimensionale array.

Oplossing: bekijk hierwaarom ze anders zijn en hoe je ermee omgaat.


Schrijven naar een letterlijke tekenreeks: char * c = "hello"; *c = 'B';

Oplossing: wijs een array toe die is geïnitialiseerd op basis van de gegevens van de letterlijke tekenreeks, waarna u ernaar kunt schrijven:

char c[] = "hello"; *c = 'B';

Schrijven naar een letterlijke tekenreeks is ongedefinieerd gedrag. Hoe dan ook, de bovenstaande conversie van een letterlijke tekenreeks naar char *is verouderd. Dus compilers zullen waarschijnlijk waarschuwen als je het waarschuwingsniveau verhoogt.


Resources maken en vervolgens vergeten ze vrij te maken als er iets wordt gegooid.

Oplossing: gebruik slimme aanwijzers zoals std::unique_ptrof std::shared_ptrzoals aangegeven door andere antwoorden.


Een object twee keer wijzigen zoals in dit voorbeeld: i = ++i;

Oplossing: het bovenstaande was bedoeld om aan ide waarde van i+1toe te kennen. Maar wat het doet, is niet gedefinieerd. In plaats van ite verhogen en het resultaat toe te wijzen, verandert het ook iaan de rechterkant. Het veranderen van een object tussen twee sequentiepunten is ongedefinieerd gedrag. Volgordepunten zijn onder meer ||, &&, comma-operator, semicolonen entering a function(niet-limitatieve lijst!). Wijzig de code in het volgende om het correct te laten werken: i = i + 1;


Diverse problemen

Vergeten streams door te spoelen voordat een blokkeerfunctie zoals sleepwordt aangeroepen.

Oplossing: spoel de stream door ofwel std::endlte streamen in plaats van \nof door stream.flush();.


Een functie declareren in plaats van een variabele.

Oplossing: het probleem doet zich voor omdat de compiler bijvoorbeeld interpreteert

Type t(other_type(value));

als een functiedeclaratie van een functie tdie Typeretourneert en met een parameter van het type other_typedie value. Je lost het op door haakjes om het eerste argument te zetten. Nu krijg je een variabele tvan het type Type:

Type t((other_type(value)));

De functie aanroepen van een vrij object dat alleen is gedeclareerd in de huidige vertaaleenheid (.cpp-bestand).

Oplossing: de standaard definieert niet de volgorde van het maken van vrije objecten (op naamruimtebereik) gedefinieerd voor verschillende vertaaleenheden. Het aanroepen van een lidfunctie op een object dat nog niet is geconstrueerd, is ongedefinieerd gedrag. U kunt in plaats daarvan de volgende functie definiëren in de vertaaleenheid van het object en deze vanuit andere oproepen:

House & getTheHouse() { static House h; return h; }

Dat zou het object op aanvraag maken en je een volledig gebouwd object geven op het moment dat je er functies op aanroept.


Een sjabloon definiëren in een .cpp-bestand, terwijl het in een ander .cpp-bestand wordt gebruikt.

Oplossing: bijna altijd krijg je fouten zoals undefined reference to .... Zet alle sjabloondefinities in een header, zodat wanneer de compiler ze gebruikt, deze al de benodigde code kan produceren.


static_cast<Derived*>(base);als base een verwijzing is naar een virtuele basisklasse van Derived.

Oplossing: een virtuele basisklasse is een basis die slechts één keer voorkomt, zelfs als deze meer dan één keer wordt geërfd door verschillende klassen indirect in een overervingsboom. Het bovenstaande is niet toegestaan ​​door de Standaard. Gebruik dynamic_cast om dat te doen en zorg ervoor dat je basisklasse polymorf is.


dynamic_cast<Derived*>(ptr_to_base);als basis niet-polymorf is

Oplossing: de standaard staat geen downcast van een aanwijzer of verwijzing toe als het doorgegeven object niet polymorf is. Het of een van zijn basisklassen moet een virtuele functie hebben.


Je functie laten accepteren T const **

Oplossing: u denkt misschien dat dit veiliger is dan het gebruik van T **, maar in feite zal het hoofdpijn veroorzaken bij mensen die T**: De standaard staat het niet toe. Het geeft een mooi voorbeeld van waarom het niet is toegestaan:

int main() {
    char const c = ’c’;
    char* pc;
    char const** pcc = &pc; //1: not allowed
    *pcc = &c;
    *pc = ’C’; //2: modifies a const object
}

Accepteer in plaats daarvan altijd T const* const*;.

Een andere (gesloten) valkuilenthread over C++, zodat mensen die ernaar zoeken ze zullen vinden, is de Stack Overflow-vraag C++-valkuilen.


Antwoord 3, autoriteit 21%

Sommigen moeten C++-boeken hebben waarmee u veelvoorkomende valkuilen in C++ kunt vermijden:

Effectieve C++
Effectievere C++
Effectieve STL

Het Effectieve STL-boek legt de vector van bools-problemen uit 🙂


Antwoord 4, autoriteit 16%

Brian heeft een geweldige lijst: ik zou toevoegen: “Markeer constructors met één argument altijd expliciet (behalve in die zeldzame gevallen dat je automatisch casten wilt).”


Antwoord 5, autoriteit 11%

Niet echt een specifieke tip, maar een algemene richtlijn: controleer je bronnen. C++ is een oude taal en er is in de loop der jaren veel veranderd. Best practices zijn ermee veranderd, maar helaas is er nog steeds veel oude informatie. Er zijn hier een aantal zeer goede boekaanbevelingen geweest – ik kan de tweede keer dat ik elk van Scott Meyers C++-boeken koop. Raak bekend met Boost en met de coderingsstijlen die in Boost worden gebruikt – de mensen die bij dat project betrokken zijn, zijn op het snijvlak van C++-ontwerp.

Vind het wiel niet opnieuw uit. Raak bekend met de STL en Boost en gebruik hun faciliteiten waar mogelijk door je eigen te rollen. Gebruik in het bijzonder STL-strings en -verzamelingen, tenzij je een heel, heel goede reden hebt om dat niet te doen. Leer auto_ptr en de Boost-bibliotheek voor slimme aanwijzers heel goed kennen, begrijp onder welke omstandigheden elk type slimme aanwijzer bedoeld is om te worden gebruikt en gebruik vervolgens slimme aanwijzers overal waar u anders onbewerkte aanwijzers zou hebben gebruikt. Je code is net zo efficiënt en veel minder vatbaar voor geheugenlekken.

Gebruik static_cast, dynamic_cast, const_cast en reinterpret_cast in plaats van casts in C-stijl. In tegenstelling tot casts in C-stijl laten ze je weten of je echt om een ​​ander type cast vraagt ​​dan je denkt. En ze vallen visueel op en waarschuwen de lezer dat er een cast plaatsvindt.


Antwoord 6, autoriteit 11%

De webpagina C++-valkuilendoor Scott Wheeler behandelt enkele van de belangrijkste valkuilen van C++.


Antwoord 7, autoriteit 8%

Twee valkuilen waarvan ik wou dat ik ze niet op de harde manier had geleerd:

(1) Veel output (zoals printf) wordt standaard gebufferd. Als je crashende code debugt en gebufferde debug-instructies gebruikt, is de laatste uitvoer die je ziet nietecht de laatste print-instructie die je in de code tegenkomt. De oplossing is om de buffer na elke foutopsporingsafdruk te spoelen (of de buffering helemaal uit te schakelen).

(2) Wees voorzichtig met initialisaties – (a) vermijd klasse-instanties als globals / statics; en (b) probeer al je lidvariabelen te initialiseren naar een veilige waarde in een ctor, zelfs als het een triviale waarde is, zoals NULL voor pointers.

Redening: de volgorde van globale objectinitialisatie is niet gegarandeerd (globals omvat statische variabelen), dus u kunt eindigen met code die niet-deterministisch lijkt te falen, omdat het ervan afhangt dat object X wordt geïnitialiseerd vóór object Y. Als u dat niet doet Als je een variabele van het primitieve type expliciet initialiseert, zoals een member bool of enum van een klasse, krijg je verschillende waarden in verrassende situaties — nogmaals, het gedrag kan erg niet-deterministisch lijken.


Antwoord 8, autoriteit 8%

Ik heb het al een paar keer genoemd, maar de boeken van Scott Meyers Effectieve C++ en Effectieve STLzijn echt hun gewicht in goud waard voor het helpen met C++.

Nu ik erover nadenk, C++ Gotchasis ook een uitstekende bron “uit de loopgraven”. Zijn item over het rollen van je eigen uitzonderingen en hoe ze moeten worden geconstrueerd, heeft me echt geholpen in één project.


Antwoord 9, autoriteit 8%

C++ gebruiken zoals C. Een cyclus voor maken en vrijgeven in de code hebben.

In C++ is dit niet uitzonderingsveilig en daarom mag de release niet worden uitgevoerd. In C++ gebruiken we RAIIom dit probleem op te lossen.

Alle bronnen die een handmatige creatie en vrijgave hebben, moeten in een object worden verpakt, zodat deze acties worden uitgevoerd in de constructor/destructor.

// C Code
void myFunc()
{
    Plop*   plop = createMyPlopResource();
    // Use the plop
    releaseMyPlopResource(plop);
}

In C++ moet dit in een object worden verpakt:

// C++
class PlopResource
{
    public:
        PlopResource()
        {
            mPlop=createMyPlopResource();
            // handle exceptions and errors.
        }
        ~PlopResource()
        {
             releaseMyPlopResource(mPlop);
        }
    private:
        Plop*  mPlop;
 };
void myFunc()
{
    PlopResource  plop;
    // Use the plop
    // Exception safe release on exit.
}

Antwoord 10, autoriteit 5%

Het boek C++ Gotchaskan nuttig zijn.


Antwoord 11, autoriteit 5%

Hier zijn een paar kuilen waarin ik de pech had om in te vallen. Dit alles heeft goede redenen die ik pas begreep nadat ik gebeten was door gedrag dat me verraste.

  • virtualfuncties in constructors zijn ‘t.

  • Schend de ODR (One Definition Rule)niet, dat is wat anonieme naamruimten zijn voor (onder andere).

  • De volgorde van initialisatie van leden hangt af van de volgorde waarin ze zijn aangegeven.

    class bar {
        vector<int> vec_;
        unsigned size_; // Note size_ declared *after* vec_
    public:
        bar(unsigned size)
            : size_(size)
            , vec_(size_) // size_ is uninitialized
            {}
    };
    
  • Standaardwaarden en virtualhebben een verschillende semantiek.

    class base {
    public:
        virtual foo(int i = 42) { cout << "base " << i; }
    };
    class derived : public base {
    public:
        virtual foo(int i = 12) { cout << "derived "<< i; }
    };
    derived d;
    base& b = d;
    b.foo(); // Outputs `derived 42`
    

Antwoord 12, autoriteit 4%

De belangrijkste valkuilen voor beginnende ontwikkelaars is het vermijden van verwarring tussen C en C++. C++ mag nooit worden behandeld als een louter betere C of C met klassen, omdat dit zijn kracht vermindert en het zelfs gevaarlijk kan maken (vooral bij gebruik van geheugen zoals in C).


Antwoord 13, autoriteit 4%

Bekijk boost.org. Het biedt veel extra functionaliteit, vooral hun slimme aanwijzerimplementaties.


Antwoord 14, autoriteit 4%

PRQA heeft een uitstekende en gratis C++-coderingsstandaardgebaseerd op boeken van Scott Meyers, Bjarne Stroustrop en Herb Sutter. Het brengt al deze informatie samen in één document.


Antwoord 15, autoriteit 4%

  1. De C++ FAQ Liteniet lezen. Het verklaart veel slechte (en goede!) praktijken.
  2. Gebruik Boostniet. Je bespaart jezelf een hoop frustratie door waar mogelijk te profiteren van Boost.

Antwoord 16, autoriteit 3%

Wees voorzichtig bij het gebruik van slimme aanwijzers en containerklassen.


Antwoord 17, autoriteit 3%

Vermijd pseudo-klassen en quasi-klassen… Overdesign eigenlijk.


Antwoord 18, autoriteit 3%

Vergeten een virtuele basisklasse destructor te definiëren. Dit betekent dat het aanroepen van deleteop een Base* het afgeleide deel niet zal vernietigen.


Antwoord 19

Houd de naamruimten recht (inclusief struct, klasse, naamruimte en gebruik). Dat is mijn grootste frustratie als het programma gewoon niet compileert.


Antwoord 20

Gebruik veel rechte wijzers om het te verknoeien. Gebruik in plaats daarvan RAII voor bijna alles, waarbij u er natuurlijk voor zorgt dat u de juiste slimme aanwijzers gebruikt. Als je ergens buiten een handle of pointer-type “delete” schrijft, doe je het hoogstwaarschijnlijk verkeerd.


Antwoord 21

Lees het boek C++ Gotchas: vermijden van veelvoorkomende problemen bij codering en ontwerp.


Antwoord 22

  • Blizpasta. Dat is een enorme die ik veel zie…

  • Niet-geïnitialiseerde variabelen zijn een grote fout die studenten van mij maken. Veel Java-mensen vergeten dat alleen het zeggen van “int counter” counter niet op 0 zet. Aangezien je variabelen in het h-bestand moet definiëren (en ze moet initialiseren in de constructor/setup van een object), is het gemakkelijk om te vergeten.

  • Off-by-one fouten op forloops / array access.

  • Objectcode niet goed opgeschoond wanneer voodoo start.


Antwoord 23

  • static_castneergeslagen op een virtuele basisklasse

Niet echt… Nu mijn misvatting: ik dacht dat Ain het volgende een virtuele basisklasse was, terwijl dat in feite niet zo is; het is, volgens 10.3.1, een polymorfe klasse. Het lijkt goed om hier static_castte gebruiken.

struct B { virtual ~B() {} };
struct D : B { };

Samengevat, ja, dit is een gevaarlijke valkuil.


Antwoord 24

Controleer altijd een aanwijzer voordat u er afstand van doet. In C kon je meestal rekenen op een crash op het punt waar je een slechte pointer negeerde; in C++ kun je een ongeldige referentie maken die crasht op een plek die ver verwijderd is van de bron van het probleem.

class SomeClass
{
    ...
    void DoSomething()
    {
        ++counter;    // crash here!
    }
    int counter;
};
void Foo(SomeClass & ref)
{
    ...
    ref.DoSomething();    // if DoSomething is virtual, you might crash here
    ...
}
void Bar(SomeClass * ptr)
{
    Foo(*ptr);    // if ptr is NULL, you have created an invalid reference
                  // which probably WILL NOT crash here
}

Antwoord 25

Een &vergeten en daardoor een kopie maken in plaats van een referentie.

Dit is mij twee keer op verschillende manieren overkomen:

  • Eén instantie bevond zich in een lijst met argumenten, waardoor een groot object op de stapel werd geplaatst met als gevolg een overloop van de stapel en een crash van het embedded systeem.

  • Ik ben de &van een instantievariabele vergeten, met als gevolg dat het object is gekopieerd. Nadat ik me als luisteraar van de kopie had geregistreerd, vroeg ik me af waarom ik nooit de callbacks van het originele object kreeg.

Beide waren nogal moeilijk te herkennen, omdat het verschil klein en moeilijk te zien is, en verder worden objecten en verwijzingen syntactisch op dezelfde manier gebruikt.


Antwoord 26

Intentie is (x == 10):

if (x = 10) {
    //Do something
}

Ik dacht dat ik deze fout nooit zelf zou maken, maar ik heb het onlangs gedaan.


Antwoord 27

Het essay/artikel Aanwijzingen, referenties en waardenis erg handig. Er wordt gesproken over het vermijden van valkuilen en goede praktijken. Je kunt ook door de hele site bladeren, die programmeertips bevat, voornamelijk voor C++.


Antwoord 28

Ik heb vele jaren besteed aan C++-ontwikkeling. Ik schreef een korte samenvattingvan de problemen die ik jaren geleden ermee had. Compilers die voldoen aan de normen zijn niet echt een probleem meer, maar ik vermoed dat de andere geschetste valkuilen nog steeds geldig zijn.


Antwoord 29

#include <boost/shared_ptr.hpp>
class A {
public:
  void nuke() {
     boost::shared_ptr<A> (this);
  }
};
int main(int argc, char** argv) {
  A a;
  a.nuke();
  return(0);
}

Other episodes