7
votes

Opérateur << - Comment détecter le dernier argument

J'écris une classe de journaux en C ++. Cette classe est un singleton. Je veux ajouter des journaux de telle manière: xxx

OK, et à l'intérieur d'un objet de journal, je veux enregistrer cette ligne entière à l'heure où le dernier argument vient (", en classe FOO "Dans cet exemple).

Comment détecter la dernière << argument? << A << B << IS_THIS_LAST << peut-être_Cela_is << or_not.

Je n'ai pas d'utiliser des étiquettes de fin.


4 commentaires

Je ne pense pas que l'opérateur de surcharge << est ce que vous voulez ici


@Falmarri: En fait, j'aime bien cette approche. C'est comment Qt utilise sa classe qdebug .


Et QT travaille contre la langue. Juste parce qu'ils ne peuvent pas dire qu'ils devraient.


@C Johnson: Vous ressentez la même chose à propos de C ++ Streams?


7 Réponses :


18
votes

Vous pouvez résoudre ce problème en n'utilisant pas de singleton. Si vous faites une fonction comme ceci:

log() << "Error: " << err_code << ", in class foo";


3 commentaires

Un autre objet quelque part doit toujours maintenir la poignée de fichier ouverte ...


@Potatoswatter: Oui. Puisqu'il utilisait un singleton, je dirais en faire un membre statique de la classe journal . D'autres solutions sont bien sûr possibles.


J'aime les techniques Raii jouant avec constructeur et destructeur, je pense que c'est vraiment élégant.



9
votes

Je aurais votre journal :: geInstance renvoie un objet proxy au lieu de l'objet journal lui-même. L'objet proxy enregistrera les données qui l'écrivent, puis dans son destructeur, cela écrira en réalité les données accumulées sur le journal.


1 commentaires

+1: Bien que les singletons puissent être laids, il vaut mieux donner des conseils qui nécessitent un refactoring moins inutile.



1
votes

Ne soyez pas trop intelligent avec vos opérateurs. Vous devez surcharger des opérateurs quand il est logique de le faire. Ici vous ne ne devriez pas. Cela a l'air étrange.

Vous devez simplement avoir une méthode statique qui ressemble à ceci: xxx

qui prend une STD :: String. Ensuite, les clients ont la mal à la tête de déterminer comment assembler la chaîne d'erreur.


16 commentaires

@Job: Non, C ++ Streams ne le font pas. Plus précisément, ils ne font rien de spécial à la dernière invocation. Un objet de flux C ++ utilise opérateur << () pour exécuter une fonction d'un argument et renvoie un objet de flux C ++ qui peut à nouveau exécuter une fonction. La surcharge de l'opérateur est soignée, mais elle n'est pas très flexible ni extensible, et il y a des cas où l'addition d'une autre exigence rend l'opérateur surcharge une mauvaise idée.


@David: Mais la réponse ne parle pas de ce que <<< / code> fait, que juste faire opérateur <<< / code> en premier lieu est bizarre. Ce que ce n'est pas.


@Gman: Je ne suis pas d'accord. Les fichiers journaux ont été utilisés pendant une longue période, avec opérateur << () et fonctionnent comme des flux standard C ++. Peu de gens considèrent que cela bizarre. La question est l'exigence supplémentaire à la fin de la déclaration.


@David: Mais peu de personnes se soucient de la manière dont l'insertion fonctionne . La nécessité de faire quelque chose à la fin de la chaîne d'insertion est un détail de mise en œuvre et n'a rien à voir avec opérateur <<< / code>. Si les flux C ++ ont renvoyé un proxy et ont ensuite fait une chose nécessaire à la fin de la chaîne, remarquez-vous? Souhaitez-vous?


@Gman: L'OP voulait faire quelque chose à la fin de la ligne, apparemment sans autre action de sa part. Vous pouvez appeler cela un détail de mise en œuvre, mais ce n'est que de tels détails de mise en œuvre qui rendent l'opérateur surcharge une mauvaise idée dans certaines situations. Le fait que l'OP ait du mal à faire ce qu'il veut faire avec la surcharge de l'opérateur suggère que la surcharge est une mauvaise idée. Quant à savoir si je remarquerais si C ++ Streams a fonctionné de cette façon ... Oui, je remarquerais beaucoup de mots et probablement avec des mots vulgaires chaque fois que je devais écrire mon propre opérateur << () .


@David: Son avoir des problèmes avec quelque chose signifie que ses connaissances pourraient être élargies et ne portant aucune incidence sur une caractéristique linguistique. Une fois presque tout le monde est confondu à quelque chose, alors nous appellerons en utilisant la langue de la langue une mauvaise idée. S'il retourna une référence à une variable locale et a demandé pourquoi son programme s'est écrasé, je suis sûr que vous ne diriez pas "suppose que vous ne devriez pas utiliser de références." Comment créerait-il votre propre opérateur <<< / code> tout plus difficile? Il suffit de changer std :: ostream dans std :: ostream_proxy et vous avez terminé. (Évidemment, ce dernier n'est pas standard.) Et mon point était avec l'utilisation, vous ne seriez pas ...


... Ou opérateur << (std :: ostream_proxy &, ...) juste en le regardant, et vous ne vous souciez pas . Vous voulez juste que ces trucs se retrouvent sur la sortie standard en quelque sorte. De même, Comment L'enregistreur fait que c'est chose est transparent pour les utilisateurs de l'interface de journalisation.


@Gman: Avec de nombreuses fonctionnalités linguistiques, ne comprenant pas comment elle s'applique signifie que votre compréhension est défectueuse. Avec la surcharge de l'opérateur, ne comprenant pas comment faire quelque chose signifie que vous n'utilisez pas la surcharge de l'opérateur. La surcharge de l'opérateur doit être utilisée transparente, ou c'est une très mauvaise idée. Faire quelque chose d'intelligent avec elle peut rendre un programme illisible réel rapide, donc si vous pensez que vous devez faire quelque chose d'intelligent, vous ne devriez pas surcharger des opérateurs. Comment un opérateur surchargé sa chose n'est pas transparent pour quiconque doit comprendre le code ou écrire un nouvel opérateur.


@David: Une réponse qui dit "ne le fais pas" au lieu de "Voici comment" est mauvais dans mes livres. Ne partageant pas la connaissance est une chose terrible à faire, pour moi. Quand quelqu'un veut faire quelque chose un mauvais chemin ("Quel est le moyen le plus propre de Typedef un pointeur?" -> "Ne pas [parce que c'est toujours déroutant]"), ça va, mais toi ou moi pourrait facilement faire cette interface simple à utiliser, alors pourquoi ne pas répandre cela? Je ne sais pas, votre seul argument semble être "quelqu'un pourrait ne pas le faire, alors ne le fais pas." Donc, personne ne devrait écrire aucun code, car tout le monde ne comprend pas le code? Pourquoi particularité des opérateurs?


@Gman: TapeFing Un pointeur est fréquemment une bonne chose. Surcharge des opérateurs pour faire des choses qui ne sont pas intuitives est presque toujours une mauvaise chose. Dans ce cas, je suive "les normes de codage C ++" de Sutter et Alexandrescu, chapitre 26 "Préservez la sémantique naturelle d'opérateurs surchargés" et FAQ 2.14 de Cline, Lomow et "C ++ FAQs" de GOU. Je n'aime pas faire référence à des livres ici, mais ce fil de commentaire n'est pas un endroit pour discuter de ce qui constitue une abus d'opérateurs surchargés.


@DAVID: EW, voulez-vous dire typedef quelque_type * quelque_type_ptr ? La seule fois que c'est généralement bon, c'est avec un partagé_ptr ou quelque chose, mais pour marquer un seul astérisque est déroutant. Je pense que vous ne séparez pas la mise en œuvre de l'interface, pouvons-nous convenir que la distinction existe? Les opérateurs fournissent une interface . Il n'y a plus de non-intuitif de faire ce que veut (insérer des données dans un flux avec opérateur <<< / code>) que d'utiliser des flux C ++ (insérez des données dans un flux avec opérateur << .) Quelle est la différence d'interface? Dit "OP détecte la fin et fait quelque chose" est pas un changement ...


... Dans l'interface, il est utilisé exactement le même. Son utilisation de opérateur <<< / code> dans l'interface est un choix parfaitement fine, comme @Job dit. La seule chose qui reste est la mise en œuvre. Nous savons tous les deux lots de trucs sur le capot dans les flux C ++ pour obtenir opérateur <<< / code> pour travailler, et nous allons bien avec cela, l'interface < / I> est la même chose. De même, le code OP renvoyant un proxy pour le faire sous le travail de la hotte, l'interface est la même. Rien dans une interface n'est effectué par un détail de mise en œuvre. Rien de l'utilisation de son opérateur ou de son interface ne nous conduit à conclure son interface est mauvais, c'est exactement le ...


... même que les flux C ++. La mise en œuvre est évidemment différente, comme insertion des mandataires de substitution, mais c'est juste une couche d'abstraction. Vous ne pouvez pas discuter que c'est mauvais, et même si vous ne vous disputeriez plus sur l'utilisation de l'opérateur, qui est dans l'interface . Vous pouvez dire "rendre les utilisateurs à terminer la ligne avec endl , pas la détecter.", Mais Notez rien dans cette phrase n'a rien à voir avec l'opérateur. Et je ne suis pas d'accord avec cela quand même, faire une interface facile à utiliser est une priorité absolue, et de la même manière que nous ne voulons pas que les gens devaient se rappeler supprimer , ce serait bien pour le sien. ..


... Les utilisateurs doivent ne pas avoir à retenir endl . Et il y a une solution extrêmement simple pour cela, alors pourquoi ne pas l'utiliser? La question concerne la mise en œuvre, l'opérateur <<< / code> appartient à l'interface, alors mentionne ainsi qu'il ne répond pas à la question.


@Gman: Si vous ne voyez pas la différence, c'est trop à traiter dans un fil de commentaire. Désolé, si vous voulez en discuter avec moi, cela devra être avec de meilleures installations.


Je ne pense pas que journal :: MESSAGE (STD :: String Message_Here); est une bonne idée ... Vous ne pouvez pas ajouter de chiffres à STD :: String, alors des opérations comme: journal :: Message ("Code d'erreur:" + code); sont impossibles (bien sûr sans surcharger cette opération).



5
votes

Vous faites le journal renvoyer un objet différent après l'opérateur <<.

 XXX                                  

0 commentaires

4
votes

Je pense que Jerry et Martin ont donné la meilleure suggestion, mais pour l'exhaustivité, la première chose à laquelle j'ai pensé était std :: endl .

Si vous implémentez log dans le système iOSTream par un StreamBuf classe, vous pouvez simplement ajouter << endl ou << Flush à la fin de la ligne. Puisque vous demandez, je suppose que vous ne l'avez pas fait.

Mais vous pouvez imiter la manière dont endl fonctionne. Ajouter un manipulateur manipulateur xxx

ou ajoutez un opérateur dédié <<< / code> xxx


0 commentaires

0
votes

Il n'y a pas de bon moyen de faire ce que vous voulez. C et C ++ ne sont tout simplement pas des langues orientées par ligne. Il n'y a pas de plus grande unité comme une "ligne de code" ou quoi que ce soit, ni des appels chaînés combinés de quelque manière que ce soit.

in c ++ l'expression "A << B << C << D" est exactement équivalente à trois appels distincts à l'opérateur <<, comme celui-ci: xxx

C'est pourquoi C ++ Onetreams utilise Endl en tant que marqueur de fin de ligne explicite; Il n'y a juste pas de moyen décent de le faire autrement.


3 commentaires

BTW, j'apprécie toutes les autres réponses qui ont créé des moyens de le faire. Créer des temporaires et des mandataires de courte durée, c'est une diversion académique amusante, mais dans la pratique, il va créer des frais généraux d'exécution supplémentaires et compliquer le débogage (de votre système de journalisation et de débogage!) Si quelque chose ne va pas. Je ne voulais pas vraiment y aller, car si vous ne le comprenez pas assez bien pour proposer vous-même l'idée, c'est un moyen plus compliqué de vous tirer dessus au pied. Donc, je me tiens toujours à mon assertion - il n'y a pas bon moyen de faire ce que vous voulez. :-)


Donc, fondamentalement au lieu de leur apprendre, vous dites «vous ne pouviez pas comprendre, n'essayez pas.»? De plus, ces mandataires seront entièrement inlinés par tout compilateur réalisé au cours de la dernière décennie, il n'y a pas de frais générale. Même s'il y en avait, une interface propre est meilleure qu'un rapide. (On peut toujours faire une interface propre rapide avec un profileur (ne pas deviner), mais une interface rapide est beaucoup plus difficile à faire propre.)


Je suis en désaccord (avec Drew). Créer des temporaires n'est pas une pratique académique mais vue assez souvent en code de commerce. Il n'y a pas de frais généraux significatifs (une autre de ces rumeurs Internet) et ce n'est pas si difficile de déboguer (car il est relativement facile d'obtenir la première fois d'écrire (donc aucune erreur)).



0
votes

Voici une solution basée sur la réponse de @ Martin-York. Légèrement modifié pour utiliser l'opérateur des membres dans les structures.

#include<sstream>
#include<iostream>

struct log_t{
    void publish(const std::string &s){
        std::cout << s << std::endl;
    }
};

struct record_t{

    struct record_appender_t
    {
        record_appender_t(record_t& record_) : record(record_) {}
        record_t& record;
        ~record_appender_t()
        {
            // Do stuff when this object is eventually destroyed
            // at the end of the expression.
            record.flush();
        }

        template<typename T>
        record_appender_t& operator<<(T const& data)
        {
            record.stream() << data;
            // Return a reference to the input so we can chain.
            // The object is thus not destroyed until the end of the stream.
            return *this;
        }
    };

    std::ostringstream message;
    log_t log;
    void flush(){
        log.publish(message.str());
    }
    std::ostringstream& stream() {
        return message;
    }
    template<typename T>
    record_appender_t operator<<(T const& data)
    {
        // Put stuff in log.
        message << data;

        // now return the object to detect the end of the statement.
        return record_appender_t(*this);
    }
};

#define LOG \
    record_t()

int main(){
    LOG << 1 << 2 << "a";
}


0 commentaires