2016-05-04 3 views
14

Ich spiele ein wenig mit Streams herum und kann mich nicht mit dem Kopf herumschlagen.Wie erkennt man, ob ein ptr immer noch eine gültige Referenz referenziert, nachdem diese Referenz den Gültigkeitsbereich verlässt

Hier haben wir eine grundlegende ostream ptr, die auf verschiedene Ausgabeströme gesetzt ist, ob es cout, cerr oder file ist.

// ostream ptr 
std::ostream* outstream; 

// set output ostream 
void setOutput(std::ostream & os) 
{ 
    outstream = &os; 
} 

// write message to ostream 
void writeData(const std::string & msg) 
{  
    *outstream << msg << '\n'; 
} 

int main (int argc, char * const argv[]) 
{ 
    // init to std out 
    setOutput(std::cout); 
    writeData("message to cout"); 

    setOutput(std::cerr); 
    writeData("message to cerr"); 

    std::ofstream fileout("test.txt", std::ofstream::out | std::ofstream::app); 
    setOutput(fileout); 
    writeData("message to file"); 
    //fileout.close(); 

    setOutput(std::cout); 
    writeData("message2 to cout"); 

    return 0; 
} 

Das obige funktioniert perfekt und zeigt die Stärke der C++ Iostream-Implementierung. Perfekt.

Da jedoch der setOutput durch Verweis festgelegt wird, muss das referenzierte Objekt im Gültigkeitsbereich bleiben. Hier zeigt sich das Problem. Ich möchte einen Weg finden, die Ausgabe auf std::cout zu setzen, wenn der Ofstream oder irgendein anderer Ostream für ungültig erklärt wird. Das heißt, das Objekt, auf das verwiesen wird, ist oder ist außerhalb des Gültigkeitsbereichs.

Zum Beispiel:

// write message to ostream 
void writeData(const std::string & msg) 
{ 
    if (/*stream or memory is invalid*/) 
    setOutput(std::cout); 

    *outstream << msg << '\n'; 
} 
// local fileout goes out of scope 
void foo() 
{ 
    std::ofstream fileout("test.txt", std::ofstream::out | std::ofstream::app); 
    setOutput(fileout); 
    writeData("message to file"); 
} 

int main (int argc, char * const argv[]) 
{ 
    setOutput(std::cout); 
    writeData("message to cout"); 

    foo(); 
    /* problem the local fileout is no longer referenced by the ostream ptr*/ 
    /* the following should be redirected to std::cout cuz of default*/ 
    writeData("message2 to cout"); 

    return 0; 
} 

Die oben ist gut, bis die foo() kehrt zur Hauptfunktion. Da geht es schief, weil das lokal definierte ofstream nicht mehr erreichbar ist.

Offensichtlich ist dies nicht ratsam und der Benutzer sollte dies realisieren. Allerdings möchte ich all dies in eine Protokollierungsklasse einbinden und so den Status des Objekts auch dann gültig halten, wenn der Verdacht auf einen solchen Missbrauch besteht. Dies führt zu einer ungültigen Zugriffsverletzung, die schwer zu finden ist.

Konkrete Frage. Gibt es eine Möglichkeit herauszufinden, ob ein ostream-ptr oder irgendein ptr-Objekt immer noch auf ein gültiges Objekt oder einen Speicherort verweist?

ps: Ich konnte Heap-Speicher verwenden und etwas mit Smart-Pointer tun, aber ehrlich gesagt würde ich es so halten will, wenn möglich

+3

"Konkrete Frage. Gibt es eine Möglichkeit, herauszufinden, ob ein ostream ptr oder irgendein ptr für diese Angelegenheit immer noch auf ein gültiges Objekt oder einen Speicherort verweist?" - Ja: Java. – nicomp

+6

@nicomp mag nicht java – Montaldo

+0

@Captain Obvlious aber es ist alles auf dem Stapel. Ich habe das versucht, aber Sie können immer noch nicht erkennen, ob es außerhalb des Geltungsbereiches afaik – Montaldo

Antwort

13

Konkrete Frage. Gibt es eine Möglichkeit herauszufinden, ob ein ostream-ptr oder irgendein ptr-Objekt immer noch auf ein gültiges Objekt oder einen Speicherort verweist?

Nein. Es gibt keine Möglichkeit, das mit rohen Zeigern herauszufinden. Nicht in Standard C++ mindestens.

Sie müssen sicherstellen, dass das spitze Objekt am Leben bleibt, solange es darauf zeigt.

Ein allgemeines Muster, das verwendet wird, um diese Garantie zu bieten, ist RAII, wie in anderen Antworten beschrieben. Ein anderer Ansatz zum Gewährleisten der Gültigkeit eines Zeigers besteht darin, einen intelligenten Zeiger anstelle eines rohen Zeigers zu verwenden. Diese sind jedoch nicht mit automatischen Variablen kompatibel.

Es wäre OK, weiterhin auf tote Objekte zu zeigen, solange Sie garantieren können, dass der Zeiger nicht dereferenziert wird. Was oft schwer zu garantieren ist, weil, wie bereits gesagt, es keine Möglichkeit gibt zu testen, ob das spitze Objekt existiert.

+0

Was ist ein "roher" Zeiger? – nicomp

+2

@nicomp 'rohe' Zeiger sind richtige Zeiger. Ich habe das "rohe" Qualifikationsmerkmal verwendet, um von intelligenten Zeigern zu unterscheiden, die auch als Zeiger aus einem abstrakten Blickwinkel betrachtet werden können, obwohl sie keine richtigen Zeiger sind.Ich musste unterscheiden, denn bei einigen intelligenten Zeigern * kann * man herausfinden, ob das spitze Objekt noch gültig ist, und bei anderen sorgt der intelligente Zeiger selbst dafür, das Objekt zu zerstören, wenn es nicht mehr referenziert wird also muss man es nicht herausfinden. – user2079303

+3

@nicomp Um auf Benutzer2078303 Kommentar zu erweitern, ist der Wunsch, in der Lage zu tun, wie die OP tun möchte, so groß, dass wir alle Arten von "Zeiger" (Klassen, die zeigen), die Garantien über die Lebensdauer von ein Objekt. Diese sind populär genug geworden, dass Leute angefangen haben, Zeiger "rohe Zeiger" zu nennen, um besonders darauf zu achten, wie wenige Garantien sie kommen, und sanft Leute zu ermutigen, mehr intelligente Zeiger zu verwenden. Ich habe auch von ihnen "native Zeiger" genannt, aber "roh" ist häufiger. –

3

Ein möglicher Ansatz ist es, eine RAII-Klasse zu erstellen, die den Strom hüllt vor Übergabe an setOutput. Diese Klasse sollte so entworfen werden, dass sie wie shared_ptr funktioniert, so dass sie eine gemeinsame ref count enthält. writeData prüft dann, ob es die einzige verbleibende Referenz hat, und wenn dies der Fall ist, zerstört es den Ostream und nimmt standardmäßig cout an.

10

Das klingt wie ein großer Anwendungsfall für RAII.

Schreiben Sie eine Klasse, die einen Dateinamen und einen std::ostream** als Parameter an seinen Konstruktor nimmt. Konstruieren Sie im Konstruktor der Klasse den Ofstream (als Member), und setzen Sie den Zeiger auf Offstream. Setzen Sie im Destruktor auf stdout.

Ersetzen Sie dann die ersten beiden Zeilen der folgenden Funktion durch eine Deklaration der neuen Klasse.

void foo() 
{ 
    std::ofstream fileout("test.txt", std::ofstream::out | std::ofstream::app); 
    setOutput(fileout); 
    writeData("message to file"); 
} 
5

Sie sollten RAII verwenden, um zu erzwingen, dass der Stream korrekt festgelegt wird, und dann auf std :: cout zurücksetzen, wenn das Objekt zerstört wird.

class OutputStream 
{ 
    protected: 
     static std::ostream*& internalGlobalStateOfOutputStream() 
     { 
      static std::ostream* out = &std::cout; 
      return out; 
     } 
    public: 
     static std::ostream& getOutputStream() 
     { 
      return *internalGlobalStateOfOutputStream(); 
     } 
}; 
template<typename T> 
class OutputStreamOwner: public OutputStream 
{ 
    T ownedStream; 
    public: 
     OutputStreamOwner(T&& obj) 
      : ownedStream(std::move(obj)) 
     { 
      internalGlobalStateOfOutputStream() = &ownedStream; 
     } 
     template<typename... Args> 
     OutputStreamOwner(Args... args) 
      : ownedStream(args...) 
     { 
      internalGlobalStateOfOutputStream() = &ownedStream; 
     } 
     ~OutputStreamOwner() 
     { 
      internalGlobalStateOfOutputStream() = & std::cout; 
     } 
     // Delete copy 
     OutputStreamOwner(OutputStreamOwner const&)   = delete; 
     OutputStreamOwner& operator(OutputStreamOwner const&) = delete; 
}; 

Die Nutzung ist:

void foo() 
{ 
    OutputStreamOwner<std::ofstream> output("test.txt", std::ofstream::out | std::ofstream::app); 

    writeData("message to file"); 
} 
1

Konkrete Frage. Gibt es eine Möglichkeit, herauszufinden, ob ein Ostream ptr oder ein beliebiges ptr verweist immer noch auf ein gültiges Objekt oder Speicherort?

ps: Ich konnte Heap-Speicher verwenden und etwas mit Smart-Pointer tun, aber ehrlich gesagt mag ich würde es so halten, wenn möglich

Nein, es gibt keine Standardmethode, wenn ein Rohzeiger testen oder Verweis bezieht sich immer noch auf ein gültiges Objekt.

RAII ist die Standard-C++ - Lösung für diese Art von Problem, so dass Sie meiner Meinung nach intelligente Zeiger betrachten sollten. Ich bin mir nicht bewusst, dass eine Bibliothek einen intelligenten Zeiger bereitstellt, der dieses spezielle Problem lösen würde, aber eine RAII-Lösung, die auf einem gemeinsamen Besitz basiert, scheint hier die beste Lösung zu sein.

2

Sie können diese Komplikationen mit einer Funktion, die den Stream als Eingabe verwendet, ganz vermeiden.

void writeData(std::ostream& os, const std::string & msg) 
{  
    os << msg << '\n'; 
} 

Sie können es weiter zu verfeinern, indem der Strom zurückkehrt, um es einer Ketten Anrufe zu ermöglichen:

std::ostream& os writeLine(std::ostream& os, const std::string & msg) 
{  
    os << msg << '\n'; 
    return os; 
} 

// declare stream 
stream << writeLine(stream, "Foo") << writeLine(stream, "Bar"); 

diese Funktion schöner und einfacher in der Tat ist, zu halten, wie Sie nicht haben sich daran erinnern, welcher Stream zu einem bestimmten Zeitpunkt eingestellt ist. Für große Programme ist dies eine wichtige Eigenschaft.

Verwandte Themen