Как я могу использовать ковариантные возвращаемые типы с интеллектуальными указателями?

У меня такой код:

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 {...}
     ...
};

Этот код не компилируется.

В визуальной студии это поднимает

C2555: переопределение типа возвращаемого значения виртуальной функции отличается и не является ковариантным

Если я не использую boost::shared_ptr, но возвращаю необработанные указатели, код компилируется (я понимаю, что это связано с ковариантные возвращаемые типы в C ++). Я вижу, что проблема в том, что boost::shared_ptr из Ret1 не является производным от boost::shared_ptr из RetInterface. Но я хочу вернуть boost::shared_ptr из Ret1 для использования в других классах, иначе я должен преобразовать возвращаемое значение после возврата.

  1. Я делаю что-то неправильно?
  2. Если нет, то почему язык такой - он должен быть расширяемым, чтобы обрабатывать преобразование между интеллектуальными указателями в этом сценарии? Есть ли желаемый обходной путь?

person amit    schedule 13.10.2008    source источник
comment
Если вы не используете boost :: shared_ptr, вы возвращаете указатели? Это управляемый C ++?   -  person Lev    schedule 13.10.2008
comment
@Lev Если я попытаюсь вернуть необработанные указатели, код компилируется, но тогда возникает проблема с управлением памятью. Нет, я не использую управляемый C ++.   -  person amit    schedule 13.10.2008
comment
Что я делаю: возвращаю необработанные указатели, но документирую, что вызывающая сторона отвечает за упаковку указателя в интеллектуальный указатель, например std::unique_ptr<Class>(obj.clone()).   -  person quant_dev    schedule 15.11.2015
comment
Я вижу, что проблема в том, что boost :: shared_ptr Ret1 не является производным от boost :: shared_ptr RetInterface Нет, это не так   -  person curiousguy    schedule 22.12.2018


Ответы (7)


Во-первых, это действительно так, как это работает в C ++: тип возвращаемого значения виртуальной функции в производном классе должен быть таким же, как в базовом классе. Существует специальное исключение: функция, возвращающая ссылку / указатель на некоторый класс X, может быть переопределена функцией, которая возвращает ссылку / указатель на класс, производный от X, но, как вы заметили, это не позволяет использовать умные указатели (например, shared_ptr), только для простых указателей.

Если ваш интерфейс RetInterface достаточно всеобъемлющий, вам не нужно знать фактический возвращаемый тип в вызывающем коде. В общем, в любом случае это не имеет смысла: причина get_r является virtual функцией, в первую очередь потому, что вы будете вызывать ее через указатель или ссылку на базовый класс AInterface, и в этом случае вы не можете знать, какой тип производный класс вернется. Если вы вызываете это с реальной A1 ссылкой, вы можете просто создать в A1 отдельную get_r1 функцию, которая будет делать то, что вам нужно.

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

В качестве альтернативы вы можете использовать шаблон посетителя или что-то вроде моей динамической двойной отправки, чтобы передать обратный вызов для возвращенного объекта, который затем может вызвать обратный вызов с правильным типом.

person Anthony Williams    schedule 13.10.2008
comment
Будет ли boost :: shared_ptr ‹Ret1› автоматически преобразовываться в boost :: shared_ptr ‹RetInterface›? Разве вам не нужно что-то вроде boost :: static_pointer_cast? - person h9uest; 11.03.2015
comment
Возврат указателя на производный класс имеет смысл с помощью методов клонирования в классах, реализующих несколько интерфейсов. - person quant_dev; 15.11.2015

Отличное решение опубликовано в это сообщение в блоге (от Рауля Борхеса)

Отрывок из бита перед добавлением поддержки множественного наследования и абстрактных методов:

template <typename Derived, typename Base>
class clone_inherit<Derived, Base> : public Base
{
public:
   std::unique_ptr<Derived> clone() const
   {
      return std::unique_ptr<Derived>(static_cast<Derived *>(this->clone_impl()));
   }

private:
   virtual clone_inherit * clone_impl() const override
   {
      return new Derived(*this);
   }
};

class concrete: public clone_inherit<concrete, cloneable>
{
};

int main()
{
   std::unique_ptr<concrete> c = std::make_unique<concrete>();
   std::unique_ptr<concrete> cc = b->clone();

   cloneable * p = c.get();
   std::unique_ptr<clonable> pp = p->clone();
}

Я рекомендую прочитать статью полностью. Это просто написано и хорошо объяснено.

person Bruce Adams    schedule 11.06.2019

Вы не можете изменить возвращаемые типы (для типов, не являющихся указателями и ссылками) при перегрузке методов в C ++. A1::get_r должен возвращать boost::shared_ptr<RetInterface>.

У Энтони Уильямса есть хороший исчерпывающий ответ.

person Mr Fooz    schedule 13.10.2008

Что насчет этого решения:

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);
}
};

e.g:

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_; }
};
person morabot    schedule 17.04.2013

Вот моя попытка:

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;
};

И небольшой пример:

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();
}

Пояснения:

Это решение подразумевает метапрограммирование и изменение классов, используемых в ковариантных интеллектуальных указателях.

Простая структура шаблона Child предназначена для привязки типа Parent и наследования. Любой класс, унаследованный от Child<T>, будет унаследован от T и определит T как Parent. Для классов, используемых в ковариантных интеллектуальных указателях, необходимо определить этот тип.

Класс has_parent используется для определения во время компиляции, определяет ли класс тип Parent или нет. Это не моя часть, я использовал тот же код, чтобы определить, существует ли метод (см. здесь)

Поскольку нам нужна ковариация с интеллектуальными указателями, мы хотим, чтобы наши интеллектуальные указатели имитировали существующую архитектуру классов. На примере проще объяснить, как это работает.

Когда CovariantSharedPtr<B> определен, он наследуется от ICovariantSharedPtr<B>, который интерпретируется как ICovariantSharedPtr<B, has_parent<B>::value>. Поскольку B наследуется от Child<A>, has_parent<B>::value истинно, поэтому ICovariantSharedPtr<B> равно ICovariantSharedPtr<B, true> и наследуется от ICovariantSharedPtr<B::Parent>, который равен ICovariantSharedPtr<A>. Поскольку A не определено Parent, has_parent<A>::value ложно, ICovariantSharedPtr<A> равно ICovariantSharedPtr<A, false> и ни от чего не наследуется.

Суть в том, что B наследуется от A, у нас ICovariantSharedPtr<B> наследуется от ICovariantSharedPtr<A>. Таким образом, любой метод, возвращающий указатель или ссылку на ICovariantSharedPtr<A>, может быть перегружен методом, возвращающим то же самое на ICovariantSharedPtr<B>.

person Grégory MALLET    schedule 11.09.2015
comment
Как насчет того, чтобы добавить какое-нибудь обоснование и объяснение тем, у кого мало времени, чтобы это расшифровать? Как это по сравнению с моработом? Merci. - person Quartz; 08.04.2016

Мистер Фуз ответил на часть 1 вашего вопроса. Часть 2, это работает так, потому что компилятор не знает, будет ли он вызывать AInterface :: get_r или A1 :: get_r во время компиляции - ему нужно знать, какое возвращаемое значение он получит, поэтому он настаивает на обоих методах возвращает тот же тип. Это часть спецификации C ++.

В качестве обходного пути, если A1 :: get_r возвращает указатель на RetInterface, виртуальные методы в RetInterface по-прежнему будут работать, как ожидалось, и соответствующий объект будет удален при уничтожении указателя. Нет необходимости в разных типах возврата.

person Mark Ransom    schedule 13.10.2008

возможно, вы могли бы использовать параметр out, чтобы обойти "ковариацию с возвращаемым boost shared_ptrs".

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

поскольку я подозреваю, что вызывающий может использовать в качестве аргумента более усовершенствованный тип shared_ptr.

person Community    schedule 09.06.2009