Jest to jedno z najczęściej zadawanych pytań, które mają różne podejścia, które są podobne, ale nie do końca podobne. Te trzy podejścia różnią się tym, o kim deklarujesz, że jesteś przyjacielem swojej funkcji - a następnie o tym, jak ją realizujesz.
ekstrawertyk
zadeklarowania wszystkich dawałaby szablonu jako przyjaciół. To jest to, co zaakceptowaliście jako odpowiedź, a także to, co proponuje większość innych odpowiedzi. W tym podejściu niepotrzebnie otwierasz swoją konkretną instancję D<T>
, deklarując znajomym wszystkie instancje operator<<
. Oznacza to, że std::ostream& operator<<(std::ostream &, const D<int>&)
ma dostęp do wszystkich elementów wewnętrznych 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
}
W introwertycy
wykazać tylko konkretnej instancji operatora wkładania jak listy znajomych. D<int>
może lubić operatora wstawiania, gdy zostanie zastosowany do niego samego, ale nie chce mieć nic wspólnego z std::ostream& operator<<(std::ostream&, const D<double>&)
.
Można to zrobić na dwa sposoby, prosty sposób bycia jako @Emery Berger proponuje, który jest inline operator --which to również dobry pomysł z innych powodów:
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>
}
};
W tej pierwszej wersji , jesteś , a nie tworząc szablonową, operator<<
, ale raczej niesformowaną funkcję dla każdego wystąpienia szablonu Test
. Znowu różnica jest subtelna, ale jest to zasadniczo równoznaczne z ręcznym dodawaniem: std::ostream& operator<<(std::ostream&, const Test<int>&)
podczas tworzenia instancji Test<int>
i innego podobnego przeciążenia podczas tworzenia Test
z double
lub z dowolnego innego typu.
Trzecia wersja jest bardziej uciążliwa. Bez inline kod i przy użyciu szablonu, można zadeklarować jeden konkretyzacji szablonu przyjacielem swojej klasie, nie otwierając sobie wszystkie inne dawałaby:
// 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> ...
}
wykorzystując ekstrawertyk
Subtelna różnica między tą trzecią opcją a pierwszą polega na tym, ile otwierasz na inne klasy. Przykładem nadużycia w ekstrawertyk wersji byłby ktoś, który chce uzyskać dostęp do swoich wewnętrznych i robi to:
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...
}
}
Było to ostatnie pytanie, które może być pouczające: http://stackoverflow.com/questions/4571611/virtual-operator/ –
@Daniel - to nie zajmuje problemu, który mam przy przeciążaniu dla szablonu klasy – starcorn
Myślę, że lepiej jest, jeśli nie zmienisz pytania z podaną odpowiedzią. Utrudnia to ustalenie pierwotnego problemu. Możesz dodać ** EDYTĘ ** na końcu ze zmianą * rozwiązaną * problem, ale uważam, że jest to mylące, gdy pytania zmieniają się w czasie i muszę wyciągnąć historię, aby zobaczyć, co faktycznie było zadawane w pierwsze miejsce. –