2013-04-03 26 views
6

Mam dwie klasy, z dwoma globalnymi friend oparator<< s.Operatory globalne i polimorfizm

class A { 
    friend std::ostream& operator<<(std::ostream& o, const A &a); 
}; 

class B: public A { 
    friend std::ostream& operator<<(std::ostream& o, const B &b); 
}; 

Jeśli go używać jak to wszystko działa poprawnie, wersja operatora B pobiera nazywane:

B b; 
std::cout << b; 

Ale jeśli mogę użyć polimorfizm, wersja A zostanie wywołany, chociaż dynamiczny typ jest B:

A* b = new B(); 
std::cout << *b; 

Jednym rozwiązaniem jest odlewanie:

std::cout << static_cast<B&>(*b); 

ale czy istnieje jakieś prostsze lub bardziej eleganckie rozwiązanie?

+2

Co powiesz na wywołanie funkcji wirtualnej w 'operator << (std :: ostream &, A const &)'? – dyp

Odpowiedz

10

Tak. Jeden operator wyjściowy i virtual print działają w klasach.

class A 
{ 
public: 
    virtual ~A() {} 
private: 
    virtual void print(std::ostream&) {} 
    friend std::ostream& operator << (std::ostream& os, const A& obj) 
    { 
     obj.print(os); 
     return os; 
    } 
}; 

class B 
{ 
private: 
    virtual void print(std::ostream&) {} 
}; 

Live example

+0

proszę dodać 'return os;' do funkcji znajomego. – scones

+1

usuń 'przyjaciela' z tej funkcji - nie musi być przyjacielem, ale może być funkcją bezpłatną;) –

+0

@ArneMertz druk jest prywatny. Jak operator << może być w tym przypadku darmowy? I dlaczego potrzebujemy drukować publicznie? – ForEveR

3

Wersje funkcji w klasach pochodnych nazywane są tylko wtedy, gdy dostęp do nich poprzez wskaźnik do klasy bazowej, jeśli je zdefiniować jako virtual ponieważ kompilator nie ma pojęcia, co klasa obiekt wskazywany przez wskaźnik faktycznie jest.

Tutaj problemem jest to, że są definiowania funkcji przyjaciela, tak że nie mogą same być wirtualne, rozwiązanie jest proste: czy realizacja jest operator<< wywołania funkcji wirtualnych w A które można następnie przeciążać w B.

2

Po prostu nie używaj znajomych. Są one jeszcze ściślejsze sprzężenie niż dziedziczenie. Szczególnie jeśli piszemy te operatory znajomych o szablony klas, można je specjalizować i prawnie uzyskać dostęp do wewnętrznych elementów twojej klasy. Z tych powodów używam tych operatorów wyłącznie jako cukru syntaktycznego i pozwalam im delegować funkcje członkowskie, które wykonują prawdziwą pracę. W ten sposób rozwiązanie problemu jest nie myślenia:

class A { 
public: 
    virtual std::ostream& printToStream(std::ostream& os) const; 
}; 

std::ostream& operator<<(std::ostream& os, A const& a) 
{ return a.printToStream(os); } 

class B: public A { 
    virtual std::ostream& printToStream(std::ostream& os) const; 
}; 

Czy inna klasa C pochodzi od A? Nie ma problemu, nie ma potrzeby ponownego definiowania cukru syntaktycznego (tj. operator<<), wystarczy zdefiniować sposób wykonywania prawdziwej pracy, tj. Zastąpić printToStream - to jest powód, dla którego zrobiłem to wirtualnie.