wort Böse Beispiele für subtil kaputten C++-Code




subtiles verhalten (7)

Überladen des Zuweisungsoperators, aber nicht korrektes Behandeln der Selbstzuweisung .

Ich benötige einige Beispiele für schlechten C ++ - Code, die Verstöße gegen bewährte Methoden veranschaulichen. Ich wollte meine eigenen Beispiele finden, aber es fällt mir schwer, Beispiele zu finden, die nicht erfunden sind und bei denen eine Falle nicht sofort offensichtlich ist (es ist schwieriger, als es scheint).

Beispiele wären etwa:

  1. Definieren Sie std::auto_ptr Kopierkonstruktor für Klassen mit std::auto_ptr Mitgliedern und verwenden Sie std::auto_ptr Mitglieder mit vorwärts deklarierten Klassen.
  2. Aufrufen von virtuellen Funktionen von einem Konstruktor oder Destruktor (direkt oder indirekt).
  3. Überladen einer Vorlagenfunktion.
  4. Zirkelverweise mit boost::shared_ptr .
  5. Schneiden.
  6. Ausnahmen von C-Rückrufen auslösen (direkt oder indirekt).
  7. Gleitkommavergleich für Gleichheit.
  8. Ausnahmesicherheit von Konstruktoren mit rohen Zeigern.
  9. Werfen von Destruktoren.
  10. Integer-Überlauf beim Kompilieren auf verschiedenen Architekturen (Nichtübereinstimmung von size_t und int ).
  11. Ungültigmachen eines Container-Iterators.

... oder irgendein anderes böses Ding, an das du denken kannst.

Ich würde mich über einige Hinweise auf vorhandene Ressourcen oder ein oder zwei Beispiele freuen.


Dieser, IMHO, ist auch schwierig:

class Base {
int _value;

public:
    Base() {
        _value = g();
    }

    virtual int f() = 0;

    int g() { return f(); }
};

class Derived: Base {   
public:
    Derived(): Base()
    { /* init Derived */ }

    int f() { /* implementation */ }
}

Ihr Code stürzt ab, weil die rein virtuelle Methode f() nicht implementiert ist. Der offensichtliche Grund dafür ist, dass Derived im Konstruktor noch nicht vollständig ist, sodass Sie das virtuelle pure f() aufrufen und vom Compiler nicht erkannt werden (normalerweise beschwert sich der Compiler, wenn ein reines virtuelles Objekt in einem Konstruktor aufgerufen wird). .

Auf jeden Fall kann es vorkommen, dass ein virtuelles Pure aufgerufen wird, wenn Sie einen komplexen Konstruktor haben, der andere Member-Funktionen aufruft, und keine Unit-Tests vorhanden sind.


Die ärgerlichste Analyse ergibt sich aus der Art und Weise, wie C ++ Dinge wie diese analysiert:

// Declares a function called "myVector" that returns a std::vector<float>.
std::vector<float> myVector(); 
// Does NOT declare an instance of std::vector<float> called "myVector"

// Declares a function called "foo" that returns a Foo and accepts an unnamed
// parameter of type Bar.
Foo foo(Bar()); 
// Does NOT create an instance of Foo called "foo" nor creates a Bar temporary

// Declares a function called "myVector" that takes two parameters, the first named
// "str" and the second unnamed, both of type std::istream_iterator<int>.
std::vector<float> myVector( 
    std::istream_iterator<int>(str),
    std::istream_iterator<int>()
);
// Does NOT create an instance of `std::vector<float>` named "myVector" while copying
// in elements from a range of iterators

Dies wird jeden überraschen, der mit dieser besonderen Eigenart der Sprache nicht vertraut ist (mich eingeschlossen, als ich anfing, C ++ zu lernen).


Was glaubst du, wird das Programm drucken?

#include <iostream>
using namespace std;

struct A {
    void f(int) { cout << "a" << endl; }
};

struct B: public A {
    void f(bool) { cout << "b" << endl; }
};

int main() {
    B b;
    b.f(true);
    b.f(1);
    A* a = &b;
    a->f(true);
    return 0;
}

Antwort: b , b , a ! Der erste Ausdruck ist offensichtlich. Der zweite ist b weil die Definition von B::f(bool) die Definition von A::f(int) verbirgt. Die dritte ist a weil eine Überlastungsauflösung beim statischen Typ auftritt.

(Quelle: Guru der Woche, aber ich kann den Artikel nicht finden.)


Code, der nicht ausnahmesicher ist, kann auf eine Weise fehlschlagen, die für die Leser von Code nicht offensichtlich ist:

// Order of invocation is undefined in this context according to the C++ standard.
// It's possible to leak a Foo or a Bar depending on the order of evaluation if one
// of the new statements throws an exception before their auto_ptrs can "own" it
accept_two_ptrs(std::auto_ptr<Foo>(new Foo), std::auto_ptr<Bar>(new Bar));

void MyClass::InvokeCallback(CallbackType cb)
{
    Foo* resource = new Foo;
    cb(resource); // If cb throws an exception, resource leaks
    delete resource;
}

Dieser ist heute Abend früher aufgetaucht . Wie @ Billy ONeal in diesem Beitrag eof() , kann das eof() eines Eingabestreams, bei dem nur auf eof() , zu einer Endlosschleife führen, wenn im Stream ein Fehler auftritt. Stattdessen sollte good() verwendet werden.

SCHLECHT:

while( !cin.eof() ) {
   getline(cin, input);
}

OK:

while( cin.good() ) {
   getline(cin, input);
}

[Kredit: @James McNellis]

AUSGEZEICHNET:

while (std::getline(std::cin, input)) {
}

#include <iostream>

class Base
{
    public:
        virtual void foo() const { std::cout << "A's foo!" << std::endl; }
};

class Derived : public Base
{
    public:
        void foo() { std::cout << "B's foo!" << std::endl; }
};

int main()
{
    Base* o1 = new Base();
    Base* o2 = new Derived();
    Derived* o3 = new Derived();

    o1->foo();
    o2->foo();
    o3->foo();
}

Und die Ausgabe ist:

A's foo!
A's foo!
B's foo!

Nicht sicher, ob es einen Namen hat, aber es ist sicher böse! : P





c++