Wann muss der Komma-Operator überladen werden?


76

Ich sehe hin und wieder Fragen zu SO über das Überladen des Kommaoperators in C ++ (hauptsächlich unabhängig von der Überladung selbst, aber Dinge wie der Vorstellung von Sequenzpunkten), und ich frage mich:

Wann sollten Sie das Komma überladen? Was sind einige Beispiele für seine praktische Verwendung?

Ich kann mir einfach keine Beispiele vorstellen, bei denen ich so etwas gesehen habe oder brauchte

foo, bar;

Ich bin gespannt, wann (wenn überhaupt) dies tatsächlich verwendet wird.


1
Da C ++ nun über eine einheitliche Initialisierungssyntax verfügt, sind die meisten dieser Techniken nicht mehr erforderlich.
Dan

Antworten:


66

Lassen Sie uns die Betonung ein wenig ändern auf:

Wann sollten Sie das Komma überladen?

Die Antwort: Niemals.

Die Ausnahme: Wenn Sie eine Vorlagen-Metaprogrammierung durchführen, operator,befindet sich ganz unten in der Rangliste der Operatoren eine spezielle Stelle, die sich zum Erstellen von SFINAE-Guards usw. als nützlich erweisen kann.

Die einzigen zwei praktischen Anwendungen, die ich beim Überladen gesehen habe, operator,sind beide in Boost :

  • Boost.Assign
  • Boost.Phoenix - hier ist es von grundlegender Bedeutung, dass Phoenix Lambdas mehrere Aussagen unterstützen können

93
Sag niemals nie.
user541686

7
Aber +1 für die Ausnahme. : P Würde es Ihnen etwas ausmachen, die Verwendung der Metaprogrammierung für Vorlagen ein wenig zu erläutern operator,? Das klingt wirklich interessant.
user541686

2
Außerdem überlastet Boost.Parameter den Kommaoperator, was eine andere Verwendung ist. Ich stimme auch zu, dass der Kommaoperator fast nie überladen werden sollte. Aufgrund seiner geringen Priorität ist es schwierig, es effektiv zu verwenden.
Paul Fultz II

2
Sie können es auch in Eigen finden.
Gabriel de Grimouard

1
Die niedrige Priorität ermöglicht die Komposition fast aller vorstellbaren Ausdrücke, ohne dass zusätzliche Klammern erforderlich sind - das ist eine sehr nette Eigenschaft dieses Operators. Es ist praktisch und im Laufe der Jahre habe ich viele Verwendungsmöglichkeiten dafür gefunden, die den Code lesbar und ausdrucksstark gemacht haben ... aber meine Regel ist, ihn nur zu verwenden, wenn er keine Überraschungen mit sich bringt und die Bedeutung des Codes selbst für offensichtlich macht Jemand, der die Dokumentation der verwendeten API nicht gelesen hat.
Unslander Monica

150

Ich habe den Komma-Operator verwendet, um Karten mit mehreren Indizes zu indizieren.

enum Place {new_york, washington, ...};

pair<Place, Place> operator , (Place p1, Place p2)
{
    return make_pair(p1, p2);
}


map< pair<Place, Place>, double> distance;

distance[new_york, washington] = 100;

33
Ich mag das wirklich sehr, +1.
ildjarn

12
Auf der anderen Seite soll damit die Tatsache überwunden werden, dass wir nur einen Parameter an übergeben können operator[]. Einige haben vorgeschlagen, dass mehrere Parameter verwendet werden können: siehe Evolution Defect Report 88 .
Morwenn

2
Es fühlt sich wie eine großartige Syntax an, die auch für die Implementierung mehrdimensionaler Arrays verwendet werden kann, aber leider nicht so gut für integrale Typen überladen ist.
Sim642

10
distance[{new_york, washington}]funktioniert ohne etwas zu überladen. Ein zusätzlicher Satz Klammern ist ein kleiner Preis, um etwas so Böses zu vermeiden!
Arthur Tacca

2
Was passiert, wenn Sie eine Funktion aufrufen foo(new_york, washington), die zwei separate Stellen als Argumente verwenden sollte?
OutOfBound

38

Boost.Assign verwendet es, damit Sie Dinge tun können wie:

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

Und ich habe gesehen, dass es für skurrile Sprach-Hacks verwendet wird. Ich werde sehen, ob ich welche finden kann.


Aha, ich erinnere mich an eine dieser skurrilen Anwendungen: das Sammeln mehrerer Ausdrücke . (Warnung, dunkle Magie.)


1
Meh, kann es nicht finden. Sehr eckiges Zeug.
GManNickG

1
Aber im Ernst, möchten Sie wirklich solchen Code schreiben? Für jemanden, der Ihren Code liest, ist dies äußerst verwirrend. Ich gehe davon aus, dass Snippet eine Abkürzung für a push_backfür diese 8 Werte ist, aber es sieht so aus, als würde 9 zu a hinzugefügt vector<int>, was keinen Sinn ergibt. Ehrlich gesagt ist dies ein starkes Gegenargument dafür, dass Boost eine "qualitativ hochwertige Bibliothek" ist. Der Code sollte klar und offensichtlich sein. Andernfalls könnte man auch so etwas implementieren T& operator--(int){ delete this; return *this; }, was wahrscheinlich auch gut funktionieren würde. Es ist für andere einfach nicht offensichtlich, was passiert.
Damon

2
Nun, Operator + = fügt nach allgemeinem Verständnis den Wert des Ausdrucks auf der rechten Seite hinzu. Der Ausdruck 1,2, ... 9 ergibt im allgemeinen Verständnis 9. Das Überladen von Operatoren untergräbt die Semantik, und obwohl sie syntaktisch gültig ist, bedeutet dies nicht, dass sie unbedingt gut ist. Das Überladen von Operatoren ist gut, wenn es den Code klar macht, aber hier macht es den Code mehrdeutig und verwirrend (zumindest nach meinem Gefühl). Bei der Zuweisung von initializer_list in C ++ 0x ist dies sehr unterschiedlich, da die geschweiften Klammern sofort deutlich machen, was los ist. Außerdem halte ich Überladungsoperator + = für einen Vektor ...
Damon

5
... als vielleicht nicht die klügste Wahl, weil es mindestens zwei gleich gültige Interpretationen dieses Operators auf einem Vektor gibt. Ich gehe davon aus, dass "Element (e) an Ende anhängen" hier gemeint ist, aber es könnte genauso gut "Operator + = für jedes Element im Vektor mit diesen Argumenten aufrufen" sein. Es kann sehr gut nur für Sätze gleicher Größe definiert werden, oder es kann den kleineren Satz auf Null erweitern, oder was auch immer ... Sie wissen es nicht, ohne die Dokumentation intensiv zu studieren, es ist nicht offensichtlich. Guter Code ist ohne Erklärung offensichtlich.
Damon

2
Als weiteres Beispiel erinnere ich mich, dass ich vor einigen Jahren auf eine überlastete String-Klasse gestoßen bin operator<=. So konnten Sie coolen Code wie schreiben str <= "foo";. Außer es ist überhaupt nicht cool, wenn die nächste Person, die Ihren Code liest, sagt "Was zur Hölle?" und es wird völlig uncool, wenn Sie zum ersten Mal eine Woche lang umsonst debuggen, weil jemand so etwas nicht wusste und schrieb if(str <= "bar").
Damon

24

Das Komma hat eine interessante Eigenschaft, da es einen Parameter vom Typ annehmen kann void. Wenn dies der Fall ist, wird der integrierte Kommaoperator verwendet.

Dies ist praktisch, wenn Sie feststellen möchten, ob ein Ausdruck den Typ void hat:

namespace detail_
{
    template <typename T>
    struct tag
    {
        static T get();
    };

    template <typename T, typename U>
    tag<char(&)[2]> operator,(T, tag<U>);

    template <typename T, typename U>
    tag<U> operator,(tag<T>, tag<U>);
}

#define HAS_VOID_TYPE(expr) \
    (sizeof((::detail_::tag<int>(), \
             (expr), \
             ::detail_::tag<char>).get()) == 1)

Ich lasse den Leser als Übung herausfinden, was los ist. Denken Sie daran, dass operator,Mitarbeiter auf der rechten Seite.



9

In SOCI - The C ++ Database Access Library wird es für die Implementierung des eingehenden Teils der Schnittstelle verwendet:

sql << "select name, salary from persons where id = " << id,
       into(name), into(salary);

Aus den Begründungs-FAQ :

F: Überladener Komma-Operator ist nur Verschleierung, ich mag es nicht.

Beachten Sie Folgendes:

"Senden Sie die Abfrage X an den Server Y und geben Sie das Ergebnis in die Variable Z ein."

Oben spielt das "und" eine Rolle des Kommas. Auch wenn das Überladen des Kommaoperators in C ++ nicht sehr beliebt ist, tun dies einige Bibliotheken, um eine knappe und leicht zu erlernende Syntax zu erreichen. Wir sind uns ziemlich sicher, dass der Kommaoperator in SOCI mit einem guten Effekt überladen wurde.


8

Ich verwende den Komma-Operator zum Drucken der Protokollausgabe. Es ist eigentlich sehr ähnlich, ostream::operator<<aber ich finde den Komma-Operator tatsächlich besser für die Aufgabe.

Also habe ich:

template <typename T>
MyLogType::operator,(const T& data) { /* do the same thing as with ostream::operator<<*/ }

Es hat diese schönen Eigenschaften

  • Der Kommaoperator hat die niedrigste Priorität. Wenn Sie also einen Ausdruck streamen möchten, werden die Dinge nicht durcheinander gebracht, wenn Sie die Klammer vergessen. Vergleichen Sie:

    myLog << "The mask result is: " << x&y; //operator precedence would mess this one up
    myLog, "The result is: ", x&y;
    

    Sie können sogar problemlos Vergleichsoperatoren im Inneren mischen, z

    myLog, "a==b: ", a==b;
    
  • Der Kommaoperator ist optisch klein. Es bringt das Lesen nicht durcheinander, wenn viele Dinge zusammengeklebt werden

    myLog, "Coords=", g, ':', s, ':', p;
    
  • Es stimmt mit der Bedeutung des Kommaoperators überein, dh "dies drucken" und dann "das drucken".


6

Eine Möglichkeit ist die Boost Assign- Bibliothek (obwohl ich mir ziemlich sicher bin, dass einige Leute diesen Missbrauch eher als eine gute Verwendung betrachten würden).

Boost Spirit überlastet wahrscheinlich auch den Komma-Operator (es überlastet fast alles andere ...)


Auf jeden Fall interessante Bibliotheken! +1
user541686

5

In diesem Sinne wurde mir eine Github-Pull-Anfrage mit Überladung durch Komma-Operatoren gesendet. Es sah ungefähr so ​​aus, als würde man folgen

class Mylogger {
    public:
            template <typename T>
            Mylogger & operator,(const T & val) {
                    std::cout << val;
                    return * this;
            }
 };

 #define  Log(level,args...)  \
    do { Mylogger logv; logv,level, ":", ##args; } while (0)

dann kann ich in meinem Code Folgendes tun:

 Log(2, "INFO: setting variable \", 1, "\"\n");

Kann jemand erklären, warum dies ein guter oder schlechter Anwendungsfall ist?


1
Ich weiß nicht, ob es schlecht ist oder nicht. Aber es wird vermieden, Code wie folgt zu schreiben : ... << "This is a message on line " << std::to_string(__LINE__) << " because variable a = " << std::to_string(a) << " which is larger than " << std::to_string(limit) << "\n". Dies ist sehr häufig bei der Meldung von Fehlern oder beim Erstellen von Nachrichten für Ausnahmen. Ich bin mir nicht sicher, ob Komma die einzige Wahl war: Jeder andere Operator hätte dies zum Beispiel operator+oder operator|oder operator&&oder sogar operator<<selbst erreichen können. Aber es ist ein interessanter Fall.
alfC

4
Ich denke, modernes C ++ würde stattdessen verschiedene Tempate verwenden.
Petter

Es ist schlecht, Fragen mit Fragen zu beantworten ;-)
Begrenzte Versöhnung

4

Eine der praktischen Anwendungen besteht darin, sie effektiv mit variablen Argumenten im Makro zu verwenden. Variable Argumente waren übrigens früher eine Erweiterung in GCC und jetzt Teil des C ++ 11-Standards.

Angenommen, wir haben ein class X, das ein Objekt vom Typ hinzufügt A. dh

class X {
  public: X& operator+= (const A&);
};

Was passiert , wenn wir ein oder mehr Objekte hinzufügen mögen die Ain X buffer;?
Zum Beispiel,

#define ADD(buffer, ...) buffer += __VA_ARGS__

Über dem Makro, wenn verwendet als:

ADD(buffer, objA1, objA2, objA3);

dann wird es erweitert zu:

buffer += objA1, objeA2, objA3;

Daher ist dies ein perfektes Beispiel für die Verwendung eines Kommaoperators, da die variablen Argumente mit demselben erweitert werden.

commaUm dies zu beheben, überladen wir den Operator und wickeln ihn +=wie folgt um

  X& X::operator, (const A& a) {  // declared inside `class X`
    *this += a;  // calls `operator+=`
  }

Vielleicht sollte es jetzt sein template<typename ... A> X& ADD(X& buff, A ... args) { int sink[]={ 0,(void(buff+=args),0)... }; return buff;}. Hinweis: Sie müssen wahrscheinlich die Optimierung der Senke mit einer (void) sink;Anweisung verhindern. Dies weicht dem Makro aus, das imo sogar noch besser ist
WorldSEnder

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.