шаблонный класс, производный от `const` специализированной версии

#c

#c

Вопрос:

Я наткнулся на этот код (упрощенная версия здесь):

     template<typename T>
    class SmartPtr;

    template<typename T>
    struct SmartPtr<const T>
    {
        const Tamp; operator*() const { return *_ptr; }
        const T* operator->() const { return _ptr; }
        const T* _ptr;
        // more member data and functions here
        // ...
    };

    template<typename T>
    struct SmartPtr : public SmartPtr<const T>
    {
        Tamp; operator*() const { return *const_cast<T*>(_ptr); }
        T* operator->() const { return const_cast<T*>(_ptr); }
    };
 

Я впервые вижу шаблонный класс, производный таким образом. Кто-нибудь может помочь мне понять, чего можно достичь с помощью этого? Мне кажется, что общая версия ведет себя так же, как и специализация, если она создается с помощью a const T , и специализация не требуется. Я что-то упускаю?

Комментарии:

1. Не должна ли неконстантная версия вместо этого иметь неконстантные методы ( Tamp; operator*(); )?

2. Можете ли вы также добавить переменные-члены указателя и как на самом деле определяются функции?

3. @MarcStevens Я добавил реализацию. Я не могу добавить слишком много информации об этом, и я надеюсь, что часть, которой нет в вопросе, не содержит соответствующей информации

4. @Mircae: Теперь более понятно, как используется базовый класс.

Ответ №1:

Общее поведение SmartPtr<T> и SmartPtr<const T> действительно идентично, и никакой специализации не потребовалось бы просто для обработки const и неконстантных версий T.

Однако ответ на ваш вопрос заключается в том, что эта конструкция допускает неявное преобразование из SmartPtr<T> в SmartPtr<const T> .

Например, функция, определенная для SmartPtr<const foo> , также может быть вызвана с помощью a SmartPtr<foo> , и, таким образом, вам не нужны две перегрузки для обработки обоих случаев:

 void HandleFoo(SmartPtr<foo> ptr);
void HandleConstFoo(SmartPtr<const foo> ptr);
void dotask()
{
    SmartPtr<foo> fooptr = new foo ...;
    ...
    HandleFoo(fooptr);
    HandleConstFoo(fooptr); // implicit conversion
}
 

Простое определение одного класса SmartPtr<T> не допускает неявного преобразования из SmartPtr<T> в SmartPtr<const T> .

Для неявного преобразования между различными типами вы обычно добавляете дополнительные функции-члены, но в этом случае для одного и того же класса из неконстантного в const вы столкнетесь с проблемами. Специализация с const T базовым классом — один из способов избежать этих проблем.

Комментарии:

1. Для иллюстрации void Foo(SmartPtr<const Quux>amp; quux); отдельно стоящая функция может принимать SmartPtr<Quux> q; Foo(q); аргумент, и он будет выполнять параметр quux.

Ответ №2:

Мне кажется, что общая версия ведет себя так же, как и специализация, если экземпляр создается с помощью const T

Если экземпляр создается с помощью a const T , основная версия шаблона вообще не используется. Будет использоваться специализация.

Если экземпляр создается с неконстантным T значением, то первичный просто повторно использует код из специализации, изменяя возвращаемые типы, чтобы иметь смысл для указателя на неконстантную часть данных.

Это также способ получить «ожидаемое» поведение между SmartPtr специализациями (вести себя так, как ведут себя стандартные необработанные указатели в отношении квалификационных преобразований). Конечно, мы можем добавить конструкторы преобразования, но не все случаи охватываются этим подходом. Рассмотрим это

 template<class U>
void frombulate(SmartPtr<const U>) {
}
 

Можем ли мы вызвать frombulate с помощью a SmartPtr<int> ? В этой реализации мы можем (поскольку преобразования производных в базовые разрешены при вычитании аргументов шаблона). Но конструктор преобразования не позволил бы нам этого, поскольку общие преобразования при выводе аргументов шаблона могут не учитываться.

Комментарии:

1. Возможно, я был недостаточно ясен To me it seems the general version behaves the same as the specialization if instantiated with a const T . Я имел в виду, что общая версия ведет себя точно так же, как специализация, если специализации не существует, а общая версия была создана с помощью const T

2. @MirceaIspas — Только изолированно. И вы должны иметь в виду, что этот метод не мешает вам изменять поведение более глубокими способами.

3. Спасибо! Теперь я спрашиваю себя, почему стандартная библиотека shared_ptr не допускает такого преобразования

4. @MirceaIspas — Боюсь, не могу ответить на этот вопрос.