Обобщенные конструкторы копирования
Почему классы, подобные shared_ptr
, имеют в своих конструкторах другой шаблон?
Например:
template<class T> class shared_ptr {
public:
template<class Y>
explicit shared_ptr(Y * p);
Я читал Scott Meyers Effective С++, пункт 45, в котором говорится, что идея заключается в том, чтобы сделать возможным их полиморфизм; т.е. построить shared_ptr<A>
из shared_ptr<B>
, если B получен из A.
Но не определяет конструктор типа
explicit shared_ptr(T * p);
достаточно? Я имею в виду, этот код работает очень хорошо:
class C1 {
};
class C2 : public C1 {
};
template<typename T>
class A
{
public:
A(T &a)
{
var1 = a;
}
T var1;
};
int main(int argc, char *argv[])
{
C2 c2;
A<C1> inst1(c2);
}
Итак, зачем нам нужен еще один шаблон для конструктора?
Ответы
Ответ 1
Без конструктора шаблонов следующий код будет иметь поведение undefined:
#include <memory>
class Base {};
class Derived : public Base {};
int main() {
std::shared_ptr<Base> ptr( new Derived );
}
Если shared_ptr
принимает только a Base*
, он вынужден в конечном итоге вызвать delete
на этом указателе Base*
. Но так как Base
не имеет виртуального деструктора, а указатель фактически указывает на Derived
, это поведение undefined.
Но на самом деле вышеприведенный код хорошо сформирован. Конструктор шаблонов для shared_ptr
принимает указатель Derived*
и сохраняет пользовательский удаляющий элемент, который вызывает delete
в исходном указателе Derived*
, что хорошо.
Ответ 2
explicit shared_ptr(T * p);
Если shared_ptr
имел только этот конструктор, он победил бы всю свою цель, которая должна обеспечить указатель с подсчетом ссылок.
Счетчик ссылок (или, скорее, указатель на него) является частью shared_ptr
. Он должен обновляться каждый раз, когда shared_ptr
создается, уничтожается или копируется. И эта последняя часть может быть достигнута только в том случае, если shared_ptr
передается в процедуру копирования. Передача голого указателя создаст shared_ptr
с номером ссылки 1.
Указатель с подсчетом ссылок используется только тогда, когда его счетчик ссылок выходит за пределы 1. С вашей схемой неясно, как это должно произойти.
Некоторые функции подсчета ссылок предоставляются стандартным (не шаблонным) конструктором копирования. Но не все.
Например,
std::shared_ptr<Derived> d;
std_shared_ptr<Base> b;
...
b = d;
не сможет скомпилироваться без конструктора "copy". Мы хотим, чтобы общие poiters предоставляли такое же полиморфное поведение, что и обычные указатели, поэтому мы хотим, чтобы приведенная выше конструкция была Just Work. Конструктор шаблонов обеспечивает это.