2016-05-04 1 views
14

Je suis en train de jouer avec des flux et je n'arrive pas à comprendre ce qui suit.Comment détecter si un PTR fait toujours référence à une référence valide après que cette référence est hors de portée

Ici, nous avons un ostream ptr de base qui est mis à différents flux de sortie, que ce soit cout, cerr ou file.

// 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; 
} 

Ce qui précède fonctionne parfaitement et montre la force de la mise en œuvre de l'iostream C++. Parfait.

Cependant, puisque le setOutput est défini par référence, l'objet référencé doit rester dans la portée. C'est là que le problème émerge. Je veux trouver un moyen de mettre par défaut la sortie à std::cout si l'ofstream ou tout autre ostream est invalidé. Autrement dit, l'objet référencé est ou est sorti de la portée.

Par exemple:

// 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; 
} 

Ce qui précède est bien jusqu'à ce que les foo() revient à la fonction principale. Là, il va horrible mal parce que le ofstream localement défini n'est plus accessible.

De toute évidence, ce n'est pas conseillé et l'utilisateur devrait s'en rendre compte. Cependant, je veux emballer tout cela dans une classe de journalisation et ainsi garder l'état de l'objet valide même si cette mauvaise utilisation pourrait se produire. Cela entraînera une violation d'accès invalide qui peut être difficile à trouver.

Question concrète. Existe-t-il un moyen de déterminer si un ostream ptr ou un ptr fait toujours référence à un objet valide ou à un emplacement mémoire?

ps: je pourrais utiliser la mémoire du tas et faire quelque chose avec des pointeurs intelligents, mais franchement je veux continuer comme ça si possible

+3

"Question concrète: existe-t-il un moyen de déterminer si un ostream ptr ou un ptr d'ailleurs fait toujours référence à un objet valide ou à un emplacement mémoire?" - oui: Java. – nicomp

+6

@nicomp n'aime pas java – Montaldo

+0

@Captain Obvlious mais tout est sur la pile. J'ai essayé cela, mais vous ne pouvez toujours pas détecter si elle est sortie de la portée – Montaldo

Répondre

13

question concrète. Existe-t-il un moyen de déterminer si un ostream ptr ou un ptr fait toujours référence à un objet valide ou à un emplacement mémoire?

Non. Il n'y a aucun moyen de comprendre cela avec des pointeurs bruts. Pas en standard C++ au moins.

Vous devrez vous assurer que l'objet pointu reste en vie tant qu'il est pointé vers.

Un motif commun utilisé pour fournir cette garantie est RAII, comme détaillé dans d'autres réponses. Une autre approche pour garantir la validité d'un pointeur consiste à utiliser un pointeur intelligent au lieu d'un pointeur brut. Cependant, ceux-ci ne sont pas compatibles avec les variables automatiques.

Il serait bon de continuer à pointer vers des objets morts tant que vous pourriez garantir que le pointeur n'est pas déréférencé. Ce qui est souvent difficile à garantir car, comme déjà dit, il n'y a aucun moyen de tester si l'objet pointé existe.

+0

Qu'est-ce qu'un pointeur «brut»? – nicomp

+2

@nicomp Les pointeurs «bruts» sont des pointeurs appropriés. J'ai utilisé le qualificatif «brut» pour distinguer les pointeurs intelligents qui peuvent également être considérés comme des pointeurs d'un point de vue abstrait, même s'ils ne sont pas des pointeurs appropriés.J'avais besoin de distinguer, car dans le cas de certains pointeurs intelligents, vous pouvez * déterminer * si l'objet pointé est toujours valide et dans le cas des autres, le pointeur intelligent se charge de détruire l'objet lorsqu'il n'est plus référencé il n'y a donc pas besoin de le comprendre. – user2079303

+3

@nicomp Pour développer le commentaire de user2078303, le désir de pouvoir faire des choses que l'OP veut faire est si grand, que nous avons inventé toutes sortes de "pointeurs" (classes qui pointent) qui fournissent des garanties sur la durée de vie de un objet. Ceux-ci sont devenus assez populaires que les gens ont commencé à appeler les pointeurs "pointeurs bruts" pour prendre soin de souligner le peu de garanties qu'ils viennent, et encourager doucement les gens à utiliser des pointeurs plus intelligents. J'ai aussi entendu parler d'eux appelés "pointeurs natifs", mais "cru" est plus commun. –

3

Une approche possible est de créer une classe RAII qui enveloppe le flux avant en le passant dans setOutput. Cette classe doit être conçue pour fonctionner comme shared_ptr de telle sorte qu'elle conserve un nombre de ref partagé. writeData vérifie ensuite si elle possède la seule référence restante et, si tel est le cas, détruit l'ostream et utilise par défaut cout.

10

Cela ressemble à un excellent cas d'utilisation pour RAII.

Ecrivez une classe qui prend un nom de fichier et un std::ostream** comme paramètres de son constructeur. Dans le constructeur de la classe, construisez le ofstream (en tant que membre) et définissez le pointeur sur le flux. Dans le destructeur, revenez à stdout.

Ensuite, remplacez les deux premières lignes de la fonction suivante par une déclaration de la nouvelle classe.

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

Vous devez utiliser RAII pour forcer la définition correcte du flux, puis revenir à std :: cout si l'objet est détruit.

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; 
}; 

L'utilisation est:

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

    writeData("message to file"); 
} 
1

question concrète. Y at-il un moyen de déterminer si un ostream ptr ou tout ptr d'ailleurs fait toujours référence à un objet valide ou emplacement de mémoire?

ps: je pouvais utiliser la mémoire de tas et faire quelque chose avec des pointeurs intelligents mais franchement je veux continuer comme ça si possible

Non, il n'y a aucun moyen standard pour tester si un pointeur brut ou référence fait toujours référence à un objet valide. RAII est la solution standard C++ pour ce type de problème, donc vous devriez regarder des pointeurs intelligents, à mon avis. Je ne connais aucun pointeur intelligent fourni par une bibliothèque qui résoudrait ce problème particulier, mais une solution RAII basée sur la propriété partagée semble la meilleure solution ici.

2

Vous pouvez éviter ces complications avec une fonction qui prend le flux en entrée.

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

Vous pouvez affiner en retournant le flux, pour permettre un aux appels de la chaîne à elle:

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"); 

En fait, cette fonction est plus agréable et plus facile à entretenir, comme vous n'avez pas se rappeler quel flux est défini à un moment donné. Pour les grands programmes, c'est une qualité importante.