4 votes

std::stringstream comme paramètre

Je suis un peu nouveau dans le langage C++. J'écris une classe utilitaire pour la journalisation vers un fichier. Elle fonctionne à merveille, sauf que j'aimerais maintenant l'améliorer en la rendant plus pratique à utiliser (par exemple, passer des flux de chaînes à une fonction de journalisation).

C'est ce que j'ai essayé et ça n'a pas marché.

définition :

void LogStream( std::stringstream i_Log ){ m_FileHandle << i_Log << std::endl; }

appeler :

m_LogObject->LogStream( "MKLBSearchEngine::Search( " << x << ", " << i_Filter << " ) - No Results Found" );

11voto

James Kanze Points 96599

Il y a plusieurs problèmes avec votre solution. Le premier est que vous transmettez stringstream par valeur, et il ne supporte pas la copie. Vous avez besoin de par référence. La seconde est qu'à l'emplacement de l'appel, la valeur de retour de l'adresse operator<< surcharges est ostream& pas stringstream et puisque stringstream n'est pas une classe de base de ostream& (c'est l'inverse l'inverse), vous ne pouvez pas initialiser l'objet stringstream (ou le stringstream& ) avec elle. Et enfin, il n'y a pas de operator<< qui prend un stringstream en tant que paramètre de droite, de sorte que l'instruction dans la section LogStream ne peut pas fonctionner. Enfin, cela va être quelque peu gênante pour l'utilisateur. Un journal de operator<< sont des non-membres, avec un ostream& en tant que premier argument, vous ne pouvez donc pas les appeler avec un temporaire comme argument de gauche. (Dans votre exemple d'appel, bien sûr, vous avez oublié de créer le std::ostringstream de toute façon ; il ne compilera pas parce qu'il n'y a pas de surcharge de l'option << qui prend un char const[] ou un char const* comme son opérande de gauche).

Il existe des solutions de contournement pour la plupart de ces problèmes. Quelque chose comme :

void LogStream( std::ostream& text )
{
    std::ostringstream& s = dynamic_cast<std::ostringstream&>(text);
    m_FileHandle << s.str() << std::endl;
}

gère tous les problèmes sauf le dernier ; ce dernier doit être traité par le client, quelque chose comme :

m_LogObject->LogStream( std::ostringstream().flush() << "..." << x );

(L'appel à std::ostream::flush() renvoie une référence non-constante à au flux, qui peut être utilisé pour initialiser d'autres flux. std::ostream& . Et bien que vous ne puissiez pas initialiser une référence non-const avec un temporaire, vous pouvez appeler une fonction membre non-const sur elle).

La maladresse de cette solution pour le code client m'incite à préférer une solution plus complexe. solution plus complexe. Je définis un fichier spécial LogStreamer classe, quelque chose comme :

class LogStreamer
{
    boost::shared_ptr< std::ostream > m_collector;
    std::ostream* m_dest;

public:
    LogStreamer( std::ostream& dest )
        , m_collector( new std::ostringstream )
        , m_dest( &dest )
    {
    }
    ~LogStreamer()
    {
        if ( m_collector.unique() ) {
            *m_dest << m_collector->str() << std::endl;
        }
    }
    template <typename T>
    LogStreamer& operator<<( T const& value )
    {
        *m_collector << value;
        return *this;
    }
};

et

LogStreamer LogStream() { return LogStreamer( m_FileHandle ); }

Le code client peut alors écrire :

m_LogObject->LogStream() << "..." << x;

Dans mon propre code : l'objet log est toujours un singleton, l'appel est à travers une macro, qui passe __FILE__ y __LINE__ à la LogStream() et la cible finale ostream est un streambuf spécial avec une fonction fonction spéciale, appelée par LogStream() qui prend un nom de fichier et un numéro de ligne, les affiche, ainsi que l'horodatage, au début de la la ligne suivante, et indente toutes les autres lignes. Un filtrage streambuf avec quelque chose comme :

class LogFilter : public std::streambuf
{
    std::streambuf* m_finalDest;
    std::string m_currentHeader;
    bool m_isAtStartOfLine;
protected:
    virtual int overflow( int ch )
    {
        if ( m_isAtStartOfLine ) {
            m_finalDest->sputn( m_currentHeader.data(), m_currentHeader.size() );
            m_currentHeader = "    ";
        }
        m_isAtStartOfLine = (ch == '\n');
        return m_finalDest->sputc( ch );
    }
    virtual int sync()
    {
        return m_finalDest->sync();
    }

public:
    LogFilter( std::streambuf* dest )
        : m_finalDest( dest )
        , m_currentHeader( "" )
        , m_isAtStartOfLine( true )
    {
    }
    void startEntry( char const* filename, int lineNumber )
    {
        std::ostringstream header;
        header << now() << ": " << filename << " (" << lineNumber << "): ";
        m_currentHeader = header.str();
    }
};

(La fonction now() renvoie, bien sûr, un std::string avec le horodatage. Ou un struct tm et vous avez écrit un << para tm .)

1voto

Let_Me_Be Points 16797

Vous avez un problème avec votre conception. Vous ne voulez pas accepter un flux comme paramètre, acceptez une chaîne de caractères, ou faites en sorte que votre classe se comporte comme un flux (ou les deux).

Si vous faites en sorte que votre objet se comporte comme un flux, alors vous faites ce qui suit :

m_LogObject << "what to log" << etc;

Pour ce faire, il suffit de remplacer l'option << opérateur.

1voto

RedX Points 7449

Votre appel devrait ressembler à

m_LogObject->LogStream( stringstream() << "MKLBSearchEngine::Search( " << x
 << ", " << i_Filter << " ) - No Results Found" );

puisque vous devez créer votre objet stringstream que vous passerez à la fonction.

Cet appel implique que vous avez déjà un flux de sortie souhaité, je vous recommande donc de modifier la conception de votre classe pour utiliser operator<< pour la journalisation, sauf s'il est déjà surchargé.

1voto

Christian Rau Points 29137

Votre appel de fonction ne fonctionnera pas, car "MKLBSearchEngine::Search( " est de type const char* et qui n'a pas de surcharge pour la fonction << opérateur. Il ne fonctionnera pas avec std::string("MKLBSearchEngine::Search( ") soit, comme aussi std::string ne dispose pas d'un tel opérateur. Ce que vous pouvez faire, c'est l'appeler avec std::stringstream("MKLBSearchEngine::Search( ") qui convertit le premier argument en un flux, de sorte que les opérateurs suivants travaillent sur ce flux. Mais comme d'autres l'ont souligné, vous devrez faire de l'argument de la fonction une référence constante, car les flux ne sont pas copiables (même dans ce cas, ce serait assez inefficace). De même, il suffit d'écrire un std::stringstream dans le fichier ne fera pas ce que vous voulez (s'il fonctionne de toute façon), au lieu de cela, vous devez prendre son contenu (le fichier sous-jacent std::string ). En résumé, votre code devrait ressembler à ceci :

void LogStream( const std::stringstream &i_Log ){ m_FileHandle << i_Log.str() << std::endl; }
...
m_LogObject->LogStream( std::stringstream("MKLBSearchEngine::Search( ") << x << ", " << i_Filter << " ) - No Results Found" );

Mais vous pouvez aussi simplement utiliser un LogString(const std::string &) et laisser l'utilisateur de cette fonction appeler stream.str() lui-même.

0voto

James Points 11054

Vous ne pouvez pas passer des objets de flux par valeur (puisqu'ils ne sont pas copiables), vous devez donc passer par (et stocker) des références :

void LogStream(std::stringstream& i_Log){
    m_FileHandle << i_Log << std::endl;
}

Cela ne fera probablement pas ce que vous attendez (cela imprimera probablement une adresse de i_Log, pour des raisons plutôt obscures).

Si votre intention est de retirer des éléments du stringstream, cela pourrait faire ce que vous voulez :

i_Log.get( *m_FileHandle.rdbuf() );

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X