2014-11-13 3 views
3

Я начал читать о шаблонах, и я обнаружил, что умные указатели используют двойные шаблоны, что-то вроде этого:Вложенные параметры шаблона

template <class T> 
class myclass 
{ 
public: 
    template <class U> 
    myclass(U* q) { /* ... */ } 
}; 

Каков смысл этого? Я знаю Шаблонные функции будут выводить U как

myclass(new whatever(3)); 

где U будет whatever*. Итак, что такое T? Какая связь между U и T?

Я запутался ...

+0

Слово не «двойное», оно «вложенное». Вы не можете так называть, вам нужен 'myclass (новый независимо (3));'. То есть вам нужно как-то обеспечить как T, так и U. –

+0

На самом деле вам понадобится 'myclass (new whatever (3));'. Аргументы шаблона класса не могут быть выведены. Какая связь между 'T' и' U' зависит от вашего кода, и я не следую строго из такого простого примера. Я думаю, что это может потребовать, чтобы 'T' был базовым классом' U', но это зависит от того, что представляет собой весь класс. – zch

+0

Это не должен быть весь класс. 'T' находится где-то в другом месте. – UmNyobe

ответ

5

Приведенный выше примерный код не устанавливает отношения между T и U.

Один аргумент типа передан шаблону класса myclass, другой - выводимый тип, переданный конструктору.

Однако, где вы его нашли (в std::shared_ptr, вероятно), это становится более интересным.

Теперь, в std::shared_ptr, тело конструктора налагает требование, чтобы U был потомком от T. Этот конструктор позволяет создать std::shared_ptr<Base> от Derived*, зная внутри конструктора, что он строится из Derived*.

Зачем нам это нужно? В конце концов, Derived* может быть преобразован в Base* вне конструктора, так почему бы не просто взять T* (aka Base*)?

Ну, a std::shared_ptr<T> - это 3 вещи в комплекте. Это T*, контрольный счетчик и функция очистки («Делетер»).

Когда счетчик ссылок уменьшен до 0, вызывается функция очистки. По умолчанию функция очистки вызывает деструктор объекта с указателем на объект.

Но какой деструктор? Ну, деструктор называется исходя из типа U, неT. При построении записывается функция уничтожения, которая знает статический тип U. Эта функция уничтожения переносится на все копии этого оригинала shared_ptr<T>, поэтому, даже если он был удален далеко, он все равно вызывает ~U вместо ~T.

Если T имеет virtual ~T() это не делает много (и на самом деле, одинаковые comdat складывание или подобные методы сделает это ничего не делать), но если он имеет не виртуальный деструктор, shared_ptr будет вызывать надлежащий деструктор (если тип фактически равен U, а не некоторый производный тип U).

shared_ptr необходимо сохранить функцию уничтожения по другим причинам (вы можете передать пользовательские функции уничтожения), поэтому у этого нет существенных накладных расходов.

+0

Святая моля, это блестяще! Спасибо Якку, я думаю, теперь понимаю. Теперь просто для уточнения, не будет ли он работать, используя просто 'T *' для бизнес-структур? Например, 'class A' и' class B: public A', общий указатель с просто 'T *' не будет работать, если он используется в 'shared_ptr (новый B);'? – newbiepp

+0

@ user75700 С помощью 'T *' конструктор не знает, что указателем является 'U'. Поэтому, когда он создает разрушитель, он может вызывать только '~ T' не' ~ U'. Принимая 'U *', он знает, что указатель «действительно» является 'U', поэтому функция уничтожения, которую он хранит, может вызывать' ~ U'. Как уже отмечалось, это в основном имеет значение, когда 'T' имеет не-' виртуальный' деструктор (если '~ U' был' final', он также включал бы оптимизации). – Yakk

+0

Совершенно ясно. Спасибо! – newbiepp

1

В вашем примере myclass является шаблон класса и конструктор myclass::myclass() является метод шаблон. Оба должны иметь тип, чтобы они могли «работать» должным образом, где «данный» также может означать, что тип выводится.

Например действительная декларация экземпляра myclass является

myclass<double> x(new int(3)); 

Здесь T = double и U = int (обратите внимание, что конструктор принимает U*). Там не должно быть отношения между U и T.

0

Связь между T и U. Вы можете создать экземпляр myclass с любым T и вызвать конструктор с любым аргументом, пока это указатель (в вашем случае):

class A {}; 
class B {}; 
myClass<A> x(new B()); // T == A, U == B 

Обратите внимание, что вы не можете явно указать U, оно может быть выведено только из аргумент.

0

Рассмотрим шаблон класса:

template<typename T> 
class Element 
{ 
    T _element; 
public: 
    CopyFrom(T t); 
}; 

Что делать, если вы хотите CopyFrom любой тип, который неT. Например:

Element<int> a; 
a.CopyFrom(10.0f); 

Здесь, для понимания int к float преобразование не представляется возможным, и только CopyFrom может это сделать (с помощью некоторых вспомогательных функций, некоторые другие внутренние функции перегрузки и т.д.) - Но, чтобы избежать потери данных предупреждение. Таким образом, вы хотите что-то вроде:

a.CopyFrom<float>(10.0f); 

Здесь вы указали тип аргумента в float - компилятор теперь будет счастлив. Для того, чтобы сделать его работу, вы бы сделать CopyFrom функцию (метод) Шаблон:

public: 
     template<typename U> 
     CopyFrom(U t); 

Теперь a имеет тип Element<int>, но CopyFrom будет CopyFrom<float>. Obviosuly, вам не нужно использовать <float>.

a.CopyFrom/*<float>*/(10.0f); // Element<int>::CopyFrom<float>(...); 
Смежные вопросы