Подтвердить что ты не робот

Ковариантные шаблоны С++

Мне кажется, что этот вопрос уже задан, но я не могу найти его на SO, и я не могу найти что-нибудь полезное в Google. Может быть, "ковариант" - это не то слово, которое я ищу, но это понятие очень похоже на ковариантные типы возврата на функции, поэтому я думаю, что это, вероятно, правильно. Вот что я хочу сделать, и это дает мне ошибку компилятора:

class Base;
class Derived : public Base;

SmartPtr<Derived> d = new Derived;
SmartPtr<Base> b = d; // compiler error

Предположим, что эти классы полностью сведены... Я думаю, вы поняли эту идею. Он не может преобразовать a SmartPtr<Derived> в SmartPtr<Base> по какой-то неясной причине. Я помню, что это нормально в С++ и многих других языках, хотя на данный момент я не могу вспомнить, почему.

Мой корневой вопрос: какой лучший способ выполнить эту операцию назначения? В настоящее время я вытаскиваю указатель из SmartPtr, явно повышая его до базового типа, а затем перенося его в новый SmartPtr соответствующего типа (обратите внимание, что это не утечка ресурсов, потому что наши доморощенные SmartPtr использует интрузивный подсчет ссылок). Это долго и беспорядочно, особенно когда мне нужно обернуть SmartPtr в еще один объект... любые ярлыки?

4b9b3361

Ответ 1

И конструктор копирования, и оператор присваивания должны иметь возможность использовать SmartPtr другого типа и пытаться скопировать указатель от одного к другому. Если типы несовместимы, компилятор будет жаловаться, и если они совместимы, вы решили свою проблему. Что-то вроде этого:

template<class Type> class SmartPtr
{
    ....
    template<class OtherType> SmartPtr(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> copy constructor

    template<class OtherType> SmartPtr<Type> &operator=(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> assignment operator
};

Ответ 2

SmartPtr<Base> и SmartPtr<Derived> являются двумя различными экземплярами шаблона SmartPtr. Эти новые классы не разделяют наследование, которое Base и Derived делают. Следовательно, ваша проблема.

Каков наилучший способ выполнения этой операции присваивания?

 SmartPtr<Base> b = d; 

Не вызывает оператор присваивания. Это вызывает копию-ctor (копия в большинстве случаев устранена) и точно так же, как если бы вы написали:

 SmartPtr<Base> b(d); 

Предоставьте копию-ctor, которая принимает SmartPtr<OtherType> и реализует ее. То же самое касается оператора присваивания. Вам придется записать copy-ctor и op = с учетом семантики SmartPtr.

Ответ 3

Шаблоны не ковариантны, и это хорошо; представьте, что произойдет в следующем случае:

vector<Apple*> va;
va.push_back(new Apple);

// Now, if templates were covariants, a vector<Apple*> could be
// cast to a vector<Fruit*>
vector<Fruit*> & vf = va;
vf.push_back(new Orange); // Bam, we just added an Orange among the Apples!

Чтобы добиться того, что вы пытаетесь сделать, класс SmartPointer должен иметь шаблонный конструктор, который принимает либо другой SmartPointer, либо указатель другого типа. Вы могли бы взглянуть на boost:: shared_ptr, что делает именно это.

template <typename T>
class SmartPointer {

    T * ptr;

  public:
    SmartPointer(T * p) : ptr(p) {}
    SmartPointer(const SmartPointer & sp) : ptr(sp.ptr) {}

    template <typename U>
    SmartPointer(U * p) : ptr(p) {}

    template <typename U>
    SmartPointer(const SmartPointer<U> & sp) : ptr(sp.ptr) {}

    // Do the same for operator= (even though it not used in your example)
};

Ответ 4

Зависит от класса SmartPtr. Если у него есть конструктор копирования (или в вашем случае оператор присваивания), который принимает SmartPtr<T>, где T - тип, с которым он был построен, то он не будет работать, потому что SmartPtr<T1> не имеет отношения к SmartPtr<T2> даже если T1 и T2 связаны наследованием.

Однако, если у SmartPtr есть шаблонный оператор конструктора/назначения копии, с параметром шаблона TOther, который принимает SmartPtr<TOther>, тогда он должен работать.

Ответ 5

Предполагая, что вы имеете контроль над классом SmartPtr, решение заключается в предоставлении шаблонного конструктора:

template <class T>
class SmartPtr
{
    T *ptr;
public:

    // Note that this IS NOT a copy constructor, just another constructor that takes 
    // a similar looking class.
    template <class O>
    SmartPtr(const SmartPtr<O> &src)
    {
        ptr = src.GetPtr();
    }
    // And likewise with assignment operator.
};

Если типы T и O совместимы, они будут работать, если они не будут, вы получите ошибку компиляции.

Ответ 6

Я думаю, что проще всего обеспечить автоматическое преобразование в другой SmartPtr в соответствии со следующим:

template <class T>
class SmartPtr
{
public:
    SmartPtr(T *ptr) { t = ptr; }
    operator T * () const { return t; }
    template <class Q> operator SmartPtr<Q> () const
    { return SmartPtr<Q>(static_cast<Q *>(static_cast<T *>(* this))); }
private:
    T *t;
};

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