Frage

Beim Debuggen eines Absturzes in einer Multithread-Anwendung habe ich das Problem schließlich in dieser Anweisung gefunden:

CSingleLock(&m_criticalSection, TRUE);

Beachten Sie, dass ein unbenanntes Objekt der Klasse CSingleLock erstellt wird und daher das kritische Abschnittsobjekt unmittelbar nach dieser Anweisung entsperrt wird.Dies ist offensichtlich nicht das, was der Programmierer wollte.Dieser Fehler wurde durch einen einfachen Tippfehler verursacht.Meine Frage ist, kann ich irgendwie verhindern, dass das temporäre Objekt einer Klasse zur Kompilierungszeit selbst erstellt wird, d. h.Der obige Codetyp sollte einen Compilerfehler erzeugen.Im Allgemeinen denke ich, dass das temporäre Objekt dieser Klasse nicht zugelassen werden sollte, wenn eine Klasse versucht, irgendeine Art von Ressourcenbeschaffung durchzuführen.Gibt es eine Möglichkeit, dies durchzusetzen?

War es hilfreich?

Lösung

Bearbeiten: Wie j_random_hacker feststellt, ist es möglich, den Benutzer zu zwingen, ein benanntes Objekt zu deklarieren, um eine Sperre aufzuheben.

Doch selbst wenn die Erstellung von Provisorien für Ihre Klasse irgendwie verboten wäre, könnte der Benutzer einen ähnlichen Fehler machen:

// take out a lock:
if (m_multiThreaded)
{
    CSingleLock c(&m_criticalSection, TRUE);
}

// do other stuff, assuming lock is held

Letztendlich muss der Benutzer die Auswirkungen einer von ihm geschriebenen Codezeile verstehen.In diesem Fall müssen sie wissen, dass sie ein Objekt erschaffen und wie lange es haltbar ist.

Ein weiterer wahrscheinlicher Fehler:

 CSingleLock *c = new CSingleLock(&m_criticalSection, TRUE);

 // do other stuff, don't call delete on c...

Was würde Sie zu der Frage veranlassen: „Gibt es eine Möglichkeit, den Benutzer meiner Klasse daran zu hindern, sie auf dem Heap zuzuweisen?“Darauf wäre die Antwort dieselbe.

In C++0x wird es eine andere Möglichkeit geben, all dies zu tun, und zwar durch die Verwendung von Lambdas.Definieren Sie eine Funktion:

template <class TLock, class TLockedOperation>
void WithLock(TLock *lock, const TLockedOperation &op)
{
    CSingleLock c(lock, TRUE);
    op();
}

Diese Funktion erfasst die korrekte Verwendung von CSingleLock.Lassen Sie die Benutzer nun Folgendes tun:

WithLock(&m_criticalSection, 
[&] {
        // do stuff, lock is held in this context.
    });

Dies ist für den Benutzer viel schwieriger zu vermasseln.Die Syntax sieht zunächst seltsam aus, aber [&] gefolgt von einem Codeblock bedeutet „Definieren Sie eine Funktion, die keine Argumente akzeptiert, und wenn ich mich namentlich auf etwas beziehe und es der Name von etwas außerhalb ist (z. B.(eine lokale Variable in der enthaltenden Funktion) lassen Sie mich über eine nicht konstante Referenz darauf zugreifen, damit ich sie ändern kann.)

Andere Tipps

Zuerst Earwicker macht einige gute Punkte - Sie können nicht jeden versehentlichen Missbrauch dieses Konstrukts verhindern.

Aber für Ihren speziellen Fall kann dies in der Tat vermieden werden. Das ist, weil C ++ ist eine (seltsam) Unterscheidung in Bezug auf temporäre Objekte machen. Free-Funktionen können Verweise auf temporäre Objekte nicht nehmen nicht-const Also, um Sperren zu vermeiden, die Blip in die und aus der Existenz, nur bewegen der Sperrcode aus dem CSingleLock Konstruktor und in eine freie Funktion (die Sie einen Freund auszusetzen Einbauten wie Methoden zu vermeiden machen können):

class CSingleLock {
    friend void Lock(CSingleLock& lock) {
        // Perform the actual locking here.
    }
};

Unlocking ist noch in dem destructor ausgeführt.

So verwenden:

CSingleLock myLock(&m_criticalSection, TRUE);
Lock(myLock);

Ja, es ist etwas unhandlich zu schreiben. Aber jetzt wird der Compiler beschweren, wenn Sie versuchen:

Lock(CSingleLock(&m_criticalSection, TRUE));   // Error! Caught at compile time.

Da die nicht-const ref Parameter von Lock() können nicht in einem temporären binden.

Vielleicht überraschend, dass Klassenmethoden können auf Provisorien arbeiten - deshalb Lock() eine kostenlose Funktion sein muss. Wenn Sie den friend Spezifizierer und die Funktionsparameter in dem oberen Schnipsel fallen zu Lock() ein Verfahren zu machen, dann wird der Compiler glücklich können Sie schreiben:

CSingleLock(&m_criticalSection, TRUE).Lock();  // Yikes!

MS COMPILER Hinweis: MSVC ++ Versionen bis zu Visual Studio .NET 2003 falsch Funktionen erlaubt zu binden nicht konstanten Referenzen in den Versionen vor VC ++ 2005 Dieses Verhalten in VC ++ 2005 und oben behoben wurde.

Nein, es gibt keine Möglichkeit, dies zu tun. Doing fast alle C ++ Code, so würde brechen, die auf die Schaffung von namenlos Provisorien stark angewiesen ist. Ihre einzige Lösung für bestimmte Klassen ist ihre Konstrukteure privat zu machen und dann immer bauen sie über eine Art Fabrik. Aber ich denke, die Heilung ist schlimmer als die Krankheit!

Ich glaube nicht.

Es ist zwar nicht eine vernünftige Sache zu tun - wie Sie mit Ihrem Bug herausgefunden haben - es gibt nichts „illegal“ über die Aussage. Der Compiler hat keine Möglichkeit zu wissen, ob der Rückgabewert der Methode ist „vital“ ist oder nicht.

Compiler sollte nicht verbieten temporäre Objekterstellung, IMHO.

Speziell Fälle wie ein Vektor schrumpfen die Sie wirklich brauchen temporäres Objekt erstellt werden.

std::vector<T>(v).swap(v);

Auch wenn es etwas schwierig ist, aber immer noch Code-Review und Unit-Tests sollten diese Probleme fangen.

Ansonsten ist hier ein armer Mann-Lösung:

CSingleLock aLock(&m_criticalSection); //Don't use the second parameter whose default is FALSE

aLock.Lock();  //an explicit lock should take care of your problem

Was ist mit den folgenden? Etwas Mißbräuche den Präprozessor, aber es ist klug genug, dass ich denke, es aufgenommen werden sollte:

class CSingleLock
{
    ...
};
#define CSingleLock class CSingleLock

Jetzt die temporären führt zu einem Fehler zu nennen vergessen, weil während der folgenden gilt C ++:

class CSingleLock lock(&m_criticalSection, true); // Compiles just fine!

Der gleiche Code, aber den Namen wegzulassen, ist nicht:

class CSingleLock(&m_criticalSection, true); // <-- ERROR!

Ich sehe, dass niemand in 5 Jahren hat sich mit der einfachsten Lösung zu kommen:

#define LOCK(x) CSingleLock lock(&x, TRUE);
...
void f() {
   LOCK(m_criticalSection);

Und jetzt nur dieses Makro verwendet für Schlösser zu schaffen. Keine Chance Provisorien mehr zu schaffen! Dies hat den zusätzlichen Vorteil, dass das Makro kann leicht mit jeder Art von Prüfung im Debug-Builds ausführen erweitert werden, beispielsweise ungeeignet rekursive Verriegelungs Erkennung Aufnahmedatei und die Leitung des Schlosses, und vieles mehr.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top