Ковариантные шаблоны C ++ - PullRequest
16 голосов
/ 12 марта 2009

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

class Base;
class Derived : public Base;

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

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

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

Ответы [ 6 ]

15 голосов
/ 12 марта 2009

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

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

 SmartPtr<Base> b = d; 

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

 SmartPtr<Base> b(d); 

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

11 голосов
/ 12 марта 2009

И конструктор копирования, и оператор присваивания должны иметь возможность брать 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
};
5 голосов
/ 12 марта 2009

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

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's not used in your example)
};
3 голосов
/ 12 марта 2009

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

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

2 голосов
/ 12 марта 2009

Если у вас есть контроль над классом 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 совместимы, это будет работать, если нет, вы получите ошибку компиляции.

0 голосов
/ 12 марта 2009

Я думаю, что проще всего обеспечить автоматическое преобразование в другой 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;
};

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

...