Вопрос

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

class Base;
class Derived : public Base;

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

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

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

Это было полезно?

Решение

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

Другие советы

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.

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top