Überladen des Friend-Operators

Lesezeit: 6 Minuten

1647183612 943 Uberladen des Friend Operators
Sternenkorn

Ich habe jetzt einige der Fragen zu meinem Problem auf StackOverflow.com gelesen, und keine davon scheint mein Problem zu lösen. Oder ich habe es vielleicht falsch gemacht … Die überladen << funktioniert, wenn ich es in eine Inline-Funktion mache. Aber wie mache ich es in meinem Fall?

warning: friend declaration std::ostream& operator<<(std::ostream&, const D<classT>&)' declares a non-template function

warning: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here) -Wno-non-template-friend disables this warning

/tmp/cc6VTWdv.o:uppgift4.cc:(.text+0x180): undefined reference to operator<<(std::basic_ostream<char, std::char_traits<char> >&, D<int> const&)'
collect2: ld returned 1 exit status

Der Code:

template <class T>
T my_max(T a, T b)
{
   if(a > b)      
      return a;
   else
      return b;
}

template <class classT>
class D
{
public:
   D(classT in)
      : d(in) {};
   bool operator>(const D& rhs) const;
   classT operator=(const D<classT>& rhs);

   friend ostream& operator<< (ostream & os, const D<classT>& rhs);
private:
   classT d;
};


int main()
{

   int i1 = 1;
   int i2 = 2;
   D<int> d1(i1);
   D<int> d2(i2);

   cout << my_max(d1,d2) << endl;
   return 0;
}

template <class classT>
ostream& operator<<(ostream &os, const D<classT>& rhs)
{
   os << rhs.d;
   return os;
}

  • Dazu gab es kürzlich eine Frage, die aufschlussreich sein könnte: stackoverflow.com/questions/4571611/virtual-operator

    – Daniel Trebbien

    11. Januar 2011 um 16:52 Uhr

  • @Daniel – es nimmt nicht das Problem auf, das ich beim Überladen für eine Vorlagenklasse habe

    – Sternkorn

    11. Januar 2011 um 16:55 Uhr

  • Ich denke, es ist besser, wenn Sie die Frage nicht mit einer gegebenen Antwort ändern. Es macht es schwieriger zu bestimmen, was das ursprüngliche Problem war. Vielleicht möchten Sie eine hinzufügen BEARBEITEN am Ende mit der Änderung, dass gelöst das Problem, aber ich finde es verwirrend, wenn sich Fragen im Laufe der Zeit ändern und ich den Verlauf aufrufen muss, um zu sehen, was überhaupt gefragt wurde.

    – David Rodríguez – Dribeas

    11. Januar 2011 um 19:13 Uhr

Uberladen des Friend Operators
David Rodríguez – Dribeas

Dies ist eine dieser häufig gestellten Fragen, die unterschiedliche Ansätze haben, die ähnlich, aber nicht wirklich gleich sind. Die drei Ansätze unterscheiden sich darin, wen Sie zum Freund Ihrer Funktion erklären – und dann, wie Sie sie implementieren.

Der Extrovertierte

Deklarieren Sie alle Instanziierungen der Vorlage als Freunde. Dies haben Sie als Antwort akzeptiert und auch die meisten anderen Antworten schlagen vor. Bei diesem Ansatz öffnen Sie unnötigerweise Ihre spezielle Instanziierung D<T> indem Sie alle zu Freunden erklären operator<< Instanziierungen. Das ist, std::ostream& operator<<( std::ostream &, const D<int>& ) hat Zugriff auf alle Interna von D<double>.

template <typename T>
class Test {
   template <typename U>      // all instantiations of this template are my friends
   friend std::ostream& operator<<( std::ostream&, const Test<U>& );
};
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& ) {
   // Can access all Test<int>, Test<double>... regardless of what T is
}

Die Introvertierten

Deklarieren Sie nur eine bestimmte Instanz des Einfügeoperators als Freund. D<int> kann den Einfügungsoperator mögen, wenn er auf sich selbst angewendet wird, aber er will nichts damit zu tun haben std::ostream& operator<<( std::ostream&, const D<double>& ).

Dies kann auf zwei Arten erfolgen, wobei der einfache Weg so ist, wie @Emery Berger vorgeschlagen hat, der den Operator inliniert – was auch aus anderen Gründen eine gute Idee ist:

template <typename T>
class Test {
   friend std::ostream& operator<<( std::ostream& o, const Test& t ) {
      // can access the enclosing Test. If T is int, it cannot access Test<double>
   }
};

In dieser ersten Version sind Sie nicht Vorlage erstellen operator<<sondern eine nicht auf Vorlagen basierende Funktion für jede Instanziierung der Test Vorlage. Auch hier ist der Unterschied subtil, aber dies entspricht im Grunde dem manuellen Hinzufügen: std::ostream& operator<<( std::ostream&, const Test<int>& ) wenn Sie instanziieren Test<int>und eine weitere ähnliche Überladung beim Instanziieren Test mit doubleoder mit jedem anderen Typ.

Die dritte Version ist umständlicher. Ohne den Code zu inlinen und mit der Verwendung einer Vorlage können Sie eine einzelne Instanziierung der Vorlage zu einem Freund Ihrer Klasse erklären, ohne sich dafür zu öffnen alle andere Instanziierungen:

// Forward declare both templates:
template <typename T> class Test;
template <typename T> std::ostream& operator<<( std::ostream&, const Test<T>& );

// Declare the actual templates:
template <typename T>
class Test {
   friend std::ostream& operator<< <T>( std::ostream&, const Test<T>& );
};
// Implement the operator
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& t ) {
   // Can only access Test<T> for the same T as is instantiating, that is:
   // if T is int, this template cannot access Test<double>, Test<char> ...
}

Ausnutzen des Extrovertierten

Der subtile Unterschied zwischen dieser dritten Option und der ersten besteht darin, wie sehr Sie sich anderen Klassen öffnen. Ein Beispiel für Missbrauch in der extrovertiert Version wäre jemand, der Zugriff auf Ihre Interna erhalten möchte und dies tut:

namespace hacker {
   struct unique {}; // Create a new unique type to avoid breaking ODR
   template <> 
   std::ostream& operator<< <unique>( std::ostream&, const Test<unique>& )
   {
      // if Test<T> is an extrovert, I can access and modify *any* Test<T>!!!
      // if Test<T> is an introvert, then I can only mess up with Test<unique> 
      // which is just not so much fun...
   }
}

  • Die Implementierung der Funktion außerhalb der Klasse ergibt eine undefinierte Referenz. Innerhalb der Klasse bekomme ich ein Problem, sobald ich die Host-Klasse explizit mit mehr als einem Typ instanziiere.

    – grat

    12. Mai 2016 um 17:43 Uhr

  • @dgrat: Ich kann keinen Code debuggen, den ich nicht sehe, aber es funktioniert. Ist der Freund von einem der Vorlagenargumente abhängig? Wenn dies nicht der Fall ist, würden Sie Probleme mit mehreren Definitionen bekommen, da verschiedene Instanziierungen genau die erzeugen würden gleich Funktion (oder eine ODR-Verletzung, wenn die Funktionssignatur dieselbe ist, aber der Körper unterschiedlich ist).

    – David Rodríguez – Dribeas

    13. Mai 2016 um 14:36 ​​Uhr

  • Dies ist nach wie vor eine meiner am häufigsten empfohlenen Anlaufstellen für Leute, die sich mit Vorlagenfunktionen (nicht nur Operatoren) befreunden möchten, nicht zuletzt aufgrund der deutlichen Unterschiede in der Art und Weise, wie ich ausgeführt werden kann, und der jeweiligen Vor- und Nachteile. Nur eine hervorragende Antwort, David. Wenn ich könnte, würde ich es ein Dutzend Mal nach oben schrauben.

    – WhozCraig

    15. August 2017 um 19:08 Uhr

  • In der dritten Version ist “operator<< “. Diese Zeile bedeutet, dass die Funktionsvorlage von operator<< als Freund der Klassenvorlage Test mit demselben T ?

    – wangdq

    5. August 2018 um 3:30 Uhr

  • @wangdq Ja, und diese Spezialisierung ist befreundet mit und nur zu, dieser befreundeten Template-Spezialisierung. Dh wenn Sie welche haben Test<T> das einzige operator << befreundet ist das operator << <T> Instanziierung. Einige willkürlich Test<U>wo U ist nicht gleichbedeutend mit Tist kein Freund operator << <T>; es Freunde operator << <U> stattdessen. Es scheint keine große Sache zu sein, aber es kann leise zu einer werden, ohne es zu merken.

    – WhozCraig

    22. August 2018 um 16:11 Uhr


Sie können so keinen Freund deklarieren, Sie müssen dafür einen anderen Vorlagentyp angeben.

template <typename SclassT>
friend ostream& operator<< (ostream & os, const D<SclassT>& rhs);

Hinweis SclassT damit es nicht schattiert classT. Beim Definieren

template <typename SclassT>
ostream& operator<< (ostream & os, const D<SclassT>& rhs)
{
  // body..
}

  • danke, das funktioniert, habe meine Frage mit diesem Code bearbeitet, ich werde dies als Antwort ankreuzen, sobald der Ticker nach unten geht.

    – Sternkorn

    11. Januar 2011 um 17:00 Uhr

  • Beachten Sie, dass dies keine bestimmte Instanziierung von deklariert operator<< als Freund, sondern eher alle Instanziierungen, einschließlich jeglicher Spezialisierung der Vorlage. Siehe die Antwort hier

    – David Rodríguez – Dribeas

    11. Januar 2011 um 19:11 Uhr

  • @starcorn Sie sollten Ihre ausgewählte Antwort ändern, um eine bessere Antwort zu geben, und das sollte die Antwort von David Rodriguez sein.

    – Rupesch Yadav.

    8. April 2016 um 11:42 Uhr

  • @Nim basierend auf der Benutzermeinung habe ich die ausgewählte Antwort für meine Frage geändert. Aber danke für deinen Beitrag, mir damals zu helfen 🙂

    – Sternkorn

    8. April 2016 um 11:44 Uhr

  • @starcorn, keine Sorge, ich habe damals auch für Davids Antwort gestimmt, es ist eine umfassendere Behandlung des Problems.

    – Nim

    11. April 2016 um 8:07 Uhr

Dies funktionierte für mich ohne Compiler-Warnungen.

#include <iostream>
using namespace std;

template <class T>
T my_max(T a, T b)
{
  if(a > b)
    return a;
  else
    return b;
}

template <class classT>
class D
{
public:
  D(classT in)
    : d(in) {};

  bool operator>(const D& rhs) const {
    return (d > rhs.d);
  }

  classT operator=(const D<classT>& rhs);

  friend ostream& operator<< (ostream & os, const D& rhs) {
    os << rhs.d;
    return os;
  }

private:
  classT d;
};


int main()
{

  int i1 = 1;
  int i2 = 2;
  D<int> d1(i1);
  D<int> d2(i2);

  cout << my_max(d1,d2) << endl;
  return 0;
}

  • Ja das habe ich schon gemacht, aber was ist wenn ich das nicht will operator<< als Inline-Funktion?

    – Sternkorn

    11. Januar 2011 um 16:56 Uhr

  • @starcorn: Ob eine Methode/Funktion inline (implizit oder explizit) markiert ist, hat wenig damit zu tun, ob die Funktion tatsächlich in den Code eingebettet ist. Daher ist dies eine bedeutungslose Sorge.

    – Martin York

    11. Januar 2011 um 18:33 Uhr

  • +1. @starcorn: Diese Lösung ist besser als die akzeptierte. Der Unterschied ist subtil, aber in der akzeptierten Antwort deklarieren Sie alle Instanziierungen (und möglichen Spezialisierungen) von operator<< als Freunde, während Sie in dieser Lösung nur Zugriff auf die Instanziierung von gewähren operator<< das hat den gleichen typ. Auch als Nebeneffekt des Definierens operator<< Innerhalb der Klasse schränken Sie die Sichtbarkeit dessen ein operator<< nur auf die Fälle, in denen eines der beiden Argumente a ist D –der Compiler berücksichtigt die nicht einmal operator<< Überladung, es sei denn, ein Argument ist D<T>.

    – David Rodríguez – Dribeas

    11. Januar 2011 um 18:50 Uhr

  • @starcorn: Ich habe hier eine Antwort hinzugefügt, die versucht, die Unterschiede in drei verschiedenen Ansätzen zu beseitigen

    – David Rodríguez – Dribeas

    11. Januar 2011 um 18:59 Uhr

Ich denke, man sollte sich erst gar nicht anfreunden.

Sie können einen öffentlichen Methodenaufruf print erstellen, etwa so (für eine Nicht-Vorlagenklasse):

std::ostream& MyClass::print(std::ostream& os) const
{
  os << "Private One" << privateOne_ << endl;
  os << "Private Two" << privateTwo_ << endl;
  os.flush();
  return os;
}

und dann außerhalb der Klasse (aber im selben Namensraum)

std::ostream& operator<<(std::ostream& os, const MyClass& myClass)
{
  return myClass.print(os);
}

Ich denke, es sollte auch für die Vorlagenklasse funktionieren, aber ich habe es noch nicht getestet.

Bitte schön:

#include <cstdlib>
#include <iostream>
using namespace std;

template <class T>
T my_max(T a, T b)
{
   if(a > b)      
      return a;
   else
      return b;
}

template <class classT>
class D
{
public:
   D(classT in)
      : d(in) {};
   bool operator>(const D& rhs) const { return d > rhs.d;};
   classT operator=(const D<classT>& rhs);

   template<class classT> friend ostream& operator<< (ostream & os, const D<classT>& rhs);
private:
   classT d;
};

template<class classT> ostream& operator<<(ostream& os, class D<typename classT> const& rhs)
{
    os << rhs.d;
    return os;
}


int main()
{

   int i1 = 1;
   int i2 = 2;
   D<int> d1(i1);
   D<int> d2(i2);

   cout << my_max(d1,d2) << endl;
   return 0;
}

  • Ich denke nicht, dass dies kompilieren sollte: template<class classT> ostream& operator<<(ostream& os, class D<typename classT> const& rhs). Ausgearbeiteter Typ ist in der Parameterdeklaration und nicht zulässig typename erfordert qualifizierte ID.

    – Gene Buschujew

    11. Januar 2011 um 17:49 Uhr

  • @ Gene: Hm. Es kompiliert für mich auf den höchsten Ebenen mit deaktivierten MS-Erweiterungen.

    – Johannes Dibling

    11. Januar 2011 um 17:54 Uhr

  • Es kompiliert nicht mit g++, und ich vertraue dem Compiler in diesem. Das zweite Argument in der operator<< ist class D<typename classT>, und das halte ich für falsch. ich würde … benutzen D<classT> stattdessen. Das Schlüsselwort class ist dort optional (99,9% der Fälle), aber die Verwendung von typename ist keine der beiden bekannten Verwendungen: es wäre als Ersatz für ungültig classund es dient der Identifizierung, dass ein abhängiger Name in einer Vorlage tatsächlich ein Typ ist.

    – David Rodríguez – Dribeas

    11. Januar 2011 um 19:08 Uhr

  • Ich denke nicht, dass dies kompilieren sollte: template<class classT> ostream& operator<<(ostream& os, class D<typename classT> const& rhs). Ausgearbeiteter Typ ist in der Parameterdeklaration und nicht zulässig typename erfordert qualifizierte ID.

    – Gene Buschujew

    11. Januar 2011 um 17:49 Uhr

  • @ Gene: Hm. Es kompiliert für mich auf den höchsten Ebenen mit deaktivierten MS-Erweiterungen.

    – Johannes Dibling

    11. Januar 2011 um 17:54 Uhr

  • Es kompiliert nicht mit g++, und ich vertraue dem Compiler in diesem. Das zweite Argument in der operator<< ist class D<typename classT>, und das halte ich für falsch. ich würde … benutzen D<classT> stattdessen. Das Schlüsselwort class ist dort optional (99,9% der Fälle), aber die Verwendung von typename ist keine der beiden bekannten Verwendungen: es wäre als Ersatz für ungültig classund es dient der Identifizierung, dass ein abhängiger Name in einer Vorlage tatsächlich ein Typ ist.

    – David Rodríguez – Dribeas

    11. Januar 2011 um 19:08 Uhr

998160cookie-checkÜberladen des Friend-Operators

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy