Обобщенные конструкторы копирования

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