2008-10-13 16 views
52

mam kod jak poniżej:Jak mogę używać typów zmiennych kowariantnych za pomocą inteligentnych wskaźników?

class RetInterface {...} 

class Ret1: public RetInterface {...} 

class AInterface 
{ 
    public: 
    virtual boost::shared_ptr<RetInterface> get_r() const = 0; 
    ... 
}; 

class A1: public AInterface 
{ 
    public: 
    boost::shared_ptr<Ret1> get_r() const {...} 
    ... 
}; 

Ten kod nie kompiluje.

w visual studio podnosi

C2555: nadrzędne Zwraca typ funkcja różni wirtualnych i nie jest kowariantna

Jeśli nie używam boost::shared_ptr ale powrót surowych wskaźników, kod kompiluje (Rozumiem, że jest to spowodowane covariant return types w C++). Widzę problem, ponieważ boost::shared_ptr z Ret1 nie pochodzi od boost::shared_ptr z RetInterface. Ale chcę zwrócić boost::shared_ptr z Ret1 do użytku w innych klasach, w przeciwnym razie po zwracaniu należy rzucić zwracaną wartość.

  1. Czy robię coś nie tak?
  2. Jeśli nie, dlaczego jest taki język - powinien być rozszerzalny do obsługi konwersji między inteligentnymi wskaźnikami w tym scenariuszu? Czy istnieje pożądane obejście tego problemu?
+0

JEŚLI nie używasz boost :: shared_ptr, czy zwrócisz wskazówki? Czy zarządza C++? – Lev

+0

@Lev Jeśli próbuję zwracać surowe wskazówki, kod się kompiluje, ale pojawia się problem z zarządzaniem pamięcią. Nie, nie używam zarządzanego C++. –

+0

Co mogę zrobić: Zwróć surowe wskaźniki, ale udokumentuj, że wywołujący jest odpowiedzialny za zawijanie wskaźnika w inteligentnym wskaźniku, np. 'std :: unique_ptr (obj.clone())'. –

Odpowiedz

20

Po pierwsze, tak właśnie działa w C++: typ zwracany funkcji wirtualnej w klasie pochodnej musi być taki sam, jak w klasie bazowej. Istnieje specjalny wyjątek, że funkcja, która zwraca referencję/wskaźnik do jakiejś klasy X, może zostać nadpisana przez funkcję, która zwraca referencję/wskaźnik do klasy wywodzącej się z X, ale jak zauważasz, to nie pozwala na wskaźników (takich jak shared_ptr), tylko dla prostych wskaźników.

Jeśli twój interfejs RetInterface jest wystarczająco obszerny, nie musisz znać rzeczywistego zwracanego typu w kodzie wywołującym. Zasadniczo i tak nie ma to sensu: powodem jest przede wszystkim to, że get_r jest funkcją virtual, ponieważ wywołasz ją przez wskaźnik lub odwołanie do klasy bazowej AInterface, w którym to przypadku nie wiesz, jaki typ klasa pochodna powróciłaby. Jeśli wywołujesz to z rzeczywistą referencją A1, możesz po prostu utworzyć oddzielną funkcję get_r1 w A1, która robi to, czego potrzebujesz.

class A1: public AInterface 
{ 
    public: 
    boost::shared_ptr<RetInterface> get_r() const 
    { 
     return get_r1(); 
    } 
    boost::shared_ptr<Ret1> get_r1() const {...} 
    ... 
}; 

Alternatywnie, można użyć odwiedzający albo coś w moim Dynamic Double Dispatch techniki zdać oddzwanianie do zwracanego obiektu, który może następnie powoływać wywołania zwrotnego z odpowiedniego typu.

+0

Zostanie zwiększona :: shared_ptr automatycznie zostanie przekonwertowana na boost :: shared_ptr ? Czy nie potrzebujesz przynajmniej czegoś takiego jak boost :: static_pointer_cast? – h9uest

+4

Powrót wskaźnika do klasy pochodnej ma sens dzięki metodom klonowania na klasach implementujących wiele interfejsów. –

1

Nie można zmienić typów zwracanych (dla typów zwracanych bez wskaźnika, niezwiązanych z odwołaniami) w przypadku przeciążania metod w C++. A1::get_r musi zwrócić wartość boost::shared_ptr<RetInterface>.

Anthony Williams ma niezłą, kompleksową answer.

-1

Mr Fooz odpowiedział części 1 twojego pytania. Część 2 działa w ten sposób, ponieważ kompilator nie wie, czy wywoła AInterface :: get_r czy A1 :: get_r w czasie kompilacji - musi wiedzieć, jaką wartość zwracaną ma uzyskać, więc nalega na obie metody zwracając ten sam typ. Jest to część specyfikacji C++.

Aby obejść ten problem, jeśli A1 :: get_r zwróci wskaźnik do RetInterface, metody wirtualne w RetInterface będą nadal działać zgodnie z oczekiwaniami, a właściwy obiekt zostanie usunięty, gdy wskaźnik zostanie zniszczony. Nie ma potrzeby stosowania różnych typów zwrotu.

-1

może można użyć parametr wyjściowy, aby ominąć „kowariancji ze zwróconych shared_ptrs impuls.

void get_r_to(boost::shared_ptr<RetInterface>&) ... 

ponieważ podejrzewam, dzwoniący może spaść w sposób bardziej wyrafinowany rodzaj shared_ptr jako argument.

+0

Niestety nie. –

1

co rozwiązanie to:

template<typename Derived, typename Base> 
class SharedCovariant : public shared_ptr<Base> 
{ 
public: 

typedef Base BaseOf; 

SharedCovariant(shared_ptr<Base> & container) : 
    shared_ptr<Base>(container) 
{ 
} 

shared_ptr<Derived> operator ->() 
{ 
    return boost::dynamic_pointer_cast<Derived>(*this); 
} 
}; 

np

struct A {}; 

struct B : A {}; 

struct Test 
{ 
    shared_ptr<A> get() {return a_; } 

    shared_ptr<A> a_; 
}; 

typedef SharedCovariant<B,A> SharedBFromA; 

struct TestDerived : Test 
{ 
    SharedBFromA get() { return a_; } 
}; 
+0

Nie używałeś 'wirtualnego'? –

1

Oto moja próba:

template<class T> 
class Child : public T 
{ 
public: 
    typedef T Parent; 
}; 

template<typename _T> 
class has_parent 
{ 
private: 
    typedef char      One; 
    typedef struct { char array[2]; } Two; 

    template<typename _C> 
    static One test(typename _C::Parent *); 
    template<typename _C> 
    static Two test(...); 

public: 
    enum { value = (sizeof(test<_T>(nullptr)) == sizeof(One)) }; 
}; 

class A 
{ 
public : 
    virtual void print() = 0; 
}; 

class B : public Child<A> 
{ 
public: 
    void print() override 
    { 
     printf("toto \n"); 
    } 
}; 

template<class T, bool hasParent = has_parent<T>::value> 
class ICovariantSharedPtr; 

template<class T> 
class ICovariantSharedPtr<T, true> : public ICovariantSharedPtr<typename T::Parent> 
{ 
public: 
    T * get() override = 0; 
}; 

template<class T> 
class ICovariantSharedPtr<T, false> 
{ 
public: 
    virtual T * get() = 0; 
}; 

template<class T> 
class CovariantSharedPtr : public ICovariantSharedPtr<T> 
{ 
public: 
    CovariantSharedPtr(){} 

    CovariantSharedPtr(std::shared_ptr<T> a_ptr) : m_ptr(std::move(a_ptr)){} 

    T * get() final 
    { 
     return m_ptr.get(); 
    } 
private: 
    std::shared_ptr<T> m_ptr; 
}; 

I trochę przykład:

class UseA 
{ 
public: 
    virtual ICovariantSharedPtr<A> & GetPtr() = 0; 
}; 

class UseB : public UseA 
{ 
public: 
    CovariantSharedPtr<B> & GetPtr() final 
    { 
     return m_ptrB; 
    } 
private: 
    CovariantSharedPtr<B> m_ptrB = std::make_shared<B>(); 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    UseB b; 
    UseA & a = b; 
    a.GetPtr().get()->print(); 
} 

Objaśnienia:

Rozwiązanie to wymaga meta-progamming i modyfikować klasy używane w kowariantna inteligentne kursory .

Prosty szablon struct Child służy do wiązania typu Parent i dziedziczenia. Każda klasa dziedzicząca po Child<T> odziedziczy po T i zdefiniuje T jako Parent. Klasy używane w kowariantnych inteligentnych wskaźnikach wymagają zdefiniowania tego typu.

Klasa has_parent służy do wykrywania czasu kompilacji, jeśli klasa definiuje typ Parent lub nie. Ta część nie jest moja, użyłem tego samego kodu, który wykrywa, czy istnieje metoda (see here)

Ponieważ chcemy kowariancji za pomocą inteligentnych wskaźników, chcemy, aby nasze inteligentne wskaźniki naśladowały istniejącą architekturę klas. Łatwiej wyjaśnić, jak to działa w przykładzie.

Po zdefiniowaniu CovariantSharedPtr<B> dziedziczy po ICovariantSharedPtr<B>, co jest interpretowane jako ICovariantSharedPtr<B, has_parent<B>::value>. Ponieważ B dziedziczy po Child<A>, has_parent<B>::value jest prawdziwe, więc ICovariantSharedPtr<B> jest ICovariantSharedPtr<B, true> i dziedziczy po ICovariantSharedPtr<B::Parent>, co oznacza ICovariantSharedPtr<A>. Ponieważ A nie ma zdefiniowanego , has_parent<A>::value jest fałszywe, ICovariantSharedPtr<A> jest ICovariantSharedPtr<A, false> i dziedziczy z niczego.

Głównym punktem jest B dziedziczy z A mamy ICovariantSharedPtr<B> dziedziczenie z ICovariantSharedPtr<A>. Tak więc każda metoda zwracająca wskaźnik lub odniesienie na ICovariantSharedPtr<A> może zostać przeciążona przez metodę zwracającą to samo na ICovariantSharedPtr<B>.

+0

A co z dodaniem uzasadnienia i wyjaśnienia dla tych, którzy mają mało czasu, aby to rozszyfrować? Jak to się ma do morabota? Merci. – Quartz