Вступление
А начнем мы с начала: в общем случае умный указатель — это некоторая надстройка над обыкновенным указателем, которая добавляет в него полезную или требуемую функциональность. Значит это все то, что он должен предоставлять все те же возможности по работе с указателем (разыменование, получение доступа к полям или функциям из-под указателя) и заниматься «грязной работой» — предотвращать утечки, избегать обращения к ранее освобожденному участку памяти. Хотелось бы сразу сказать, что мне случалось видеть создание объектов умного указателя только для адресов, под которыми лежат объекты классов, что верно, ведь зачистки памяти под указателями подошел бы и обычный MemoryGuard, которому бы хватило одного delete.
Ближе к делу
Итак, перейдем непосредственно к реализации Shared Pointer. Сперва вести учет ресурсов не будем. Требования к классу будут следующие:
— Наличие конструктора, принимающего указатель на ресурс.
— Перегруженные операторы -> и *.
— Акссесор, позволяющий получить адрес ресурса.
Поскольку класс шаблонный, описание и реализацию можно выполнить в одному hpp-файле. Операторы -> и * могут быть определенные как члены класса, ведь слева от них будет всегда находится объект нашего умного указателя.
template<class T>
class shr_ptr
{
        T* resource;
public:
        shr_ptr(T* res=NULL):resource(res)
        {     
        } 
        ~shr_ptr()
        {
                delete resource;
        }
        T* operator ->() const 
        {
                return resource;
        }
        T& operator * () const 
        {
                return *resource;
        }
        T* get () const 
        {
                return resource;
        }
};
Такая реализация уже позволит нам безболезненно создавать указатели на объекты других классов и быть уверенными в том, что утечек не будет. Кстати, проверка утечек осуществляется с помощью функции _CrtDumpMemoryLeaks. Нижеуказанный фрагмент кода отлично отработает.
    int main()
    {
        {
            shr_ptr <SomeClass> sh (new SomeClass);
            sh->do_operation();
        }
        if(_CrtDumpMemoryLeaks())
            cout<<"LEAKS";
    }
У такой реализации есть явный недостаток — стоит нам только создать несколько объектов умного указателя с помощью конструктора копирования, как тотчас же мы столкнемся с проблемой обращения к уже освобожденному куску памяти. Для решения оной можно воспользоваться счётчиком, который будет вести учет одинаковых ресурсов. Счетчик будет создаваться при срабатывании конструктора с параметром и увеличивается в конструкторе копирования и операторе присваивания.
template<class T>
class shr_ptr
{
        T* resource;
        int* count ;
public:
        shr_ptr():resource(NULL),count(NULL)
        {
                
        } 
        shr_ptr(T* res):resource(res)
        {
                count = new int(1);
        } 
        shr_ptr(const shr_ptr<T>& R)
        {
                if(R.resource!=NULL)
		{
			resource = R.resource;
			count = R.count;
			*count = *R.count+1;
		}
        } 
        ~shr_ptr()
        {
                if(resource!=NULL && --*(count)==0)
		{
			delete resource;
			delete count;
			resource = NULL;
		}
        }
       
       shr_ptr<T>& operator = (const shr_ptr<T>& R)
       {
                if(R.resource != this->resource)
		{
			shr_ptr<T> tmp (R);
			char sBuff[sizeof(shr_ptr<T>)];
			memcpy(sBuff,this,sizeof(shr_ptr<T>));
			memcpy(this,&tmp,sizeof(shr_ptr<T>));
			memcpy(&tmp,sBuff,sizeof(shr_ptr<T>));
		}
                return *this;
       }
        T* operator ->() const 
        {
                return resource;
        }
        T& operator * () const 
        {
                return *resource;
        }
        T* get () const 
        {
                return resource;
        }
};
Как вы можете видеть, счетчик создается для каждого нового ресурса, увеличивается при повторном создании и уменьшается при вызове деструктора.
После этого создав в main несколько объектов умного указателя с одинаковыми ресурсами мы гарантировано очистим память только один раз, то есть программа успешно отработает. Все благодаря нехитрому счетчику. Пример:
    int main()
    {
        {
            shr_ptr <SomeClass> sh (new SomeClass);
            shr_ptr <SomeClass> sh1(sh);
            shr_ptr <SomeClass> sh2 (new SomeClass);
            sh = sh2; 
            sh->do_operation();
            sh1->do_operation();
            sh2->do_operation();
        }
        if(_CrtDumpMemoryLeaks())
            cout<<"LEAKS";
    }
Спасибо за внимание!
Комментарии (26)
 - Bonce14.11.2016 13:29+1- При переопределении оператора "=" возврат значения будет происходить не во всех случаях. Наверное, следует добавить return *this; последней строкой. 
 - TheCalligrapher14.11.2016 13:29+1- Не совсем понятно, зачем было "замусоривать" статью для начинающих (как вы сам сказали), некрасивым хаком с 'memcpy' в операторе присваивания? Неужели нельзя было выполнить обмен буквально? 
 - skynowa14.11.2016 13:30+1- Зачем count создавать динамически?  - kartograph14.11.2016 13:33- Отвечу сразу всем: счетчик создаю динамически, потому что создав оный на стеке второй вариант main не сработает, ресурс первого указателя будет возвращен и образуется висячий указатель sh1, появление которого мы хотели избежать. 
 
 - kachsheev14.11.2016 13:30- int* count
 Зачем? Ведь можно было создавать счётчик на стеке.
 
 - resource(NULL)
 А как же nullptr? - bfDeveloper14.11.2016 13:49- На стеке нельзя, счётчик должен быть общий между всеми указателями на один и тот же объект 
 
 - bfDeveloper14.11.2016 13:54+2- Как отметили выше, для новичков статья не очень. Рассказали бы лучше зачем это надо, где использовать, а где не надо. Про существующие shared_ptr с его thread safe; про unique_ptr, который гораздо более предпочтителен, про weak_ptr и второй счётчик ссылок в shared_ptr, чтобы вовремя удалять count. 
 А новичкам, да и вообще всем рекомендую Scott Meyers: Modern Effective C++ . Там есть глава про умные указатели со всеми подробностями. Да и все остальные главы отличные. - kartograph14.11.2016 17:31- Даже на Хабре есть достаточно хорошая статья, которую я бы однозначно рекомендовал. Единственное чего там не было — вышеописанного счетчика. 
 
 - Chaos_Optima14.11.2016 16:18- Ужас, мало того что код написан с ошибками 
 - T* resource; int* count // ; где? public: .... shr_ptr(const shr_ptr<T>& R): // : - ?
 Может стоило хотя бы скомпилить код?
 Ну и да утечка памяти в статье про умные указатели, шикарно.
 Оператор копирования дырявый
 прув: https://ideone.com/ajkyDX
 Зачем такие статьи, непонятно. Минус. - kartograph14.11.2016 16:38-1- Все исправлено, ликов нет, можете проверить.  - Chaos_Optima14.11.2016 16:59+1- Неужели так трудно пройтись компилятором перед тем как выкладывать? 
 - char sBuff[sizeof(SharedPointer<T>)]; // SharedPointer откуда взялся?
 Ну и да утечка по прежнему есть
 - shr_ptr<A> sh1 = new A; shr_ptr<A> sh2 = new A; shr_ptr<A> sh3 = sh2; sh1 = sh2;
 выдаст
 created
 created
 deleted
 
 И да зачем memcpy использовать? это крайне странно - kartograph14.11.2016 17:12- Здесь уже вы не правы, в вашем примере все три указателя в итоге будут ссылаться на один ресурс, то есть у первого ресурса счетчик 1, значит он удалится сразу после sh1=sh2, а дальше получится у sh1 счетчик равен 3, у sh2 — 1, у sh3 — 2, стало быть, удаление пройдет нормально и гарантировано только один раз. Попробуйте определить область жизни ваших умных указателей, чтобы деструктор каждого сработал до return.  - Chaos_Optima14.11.2016 17:15- да простите, мемкопи неправильно просмотрел, но всё равно это не отменяет того как ужасно написан код  - kartograph14.11.2016 17:22-1- Насчет memcpy — это довольно удобный способ расписать оператор присваиванию, впрочем, буду рад, если предложите что-небудь более оптимальное или скинете ссылку на ресурс с информацией, тогда я тотчас же изменю реализацию. А какие у вас еще претензии к коду?  - Chaos_Optima14.11.2016 17:31- Код присваивания должен был выглядеть как минимум так 
 - shr_ptr<T>& operator = (const shr_ptr<T>& R) { if (R.resource != resource) { *counter -= 1; if (!counter) { delete resource; delete count; } resource = R.resource; count = R.count; *count += 1; } return *this; }
 
 Во вторых у вас попрежнему есть утечка
 shr_ptr sh1 = NULL;
 при выходе из скоупа у вас утечёт int* count
 
 В третьих
 - shr_ptr(const shr_ptr<T>& R) { if (R.resource != NULL) { resource = R.resource; count = R.count; *count = *R.count + 1; } }
 если R.resource будет равен нулю то ваши поля будут не инициализированны, при тестах вам везло что они инициализировались 0 но в компилятор не гарантирует что там будут 0 а не мусор какой нибудь, в следствии чего можно легко словить сегфолт, или в многопоточной среде, вообще ужас программиста, плавующий баг. - kartograph14.11.2016 17:45- Если вам приятнее видеть реализацию оператора = в таком виде, значит изменю, хоть теперешняя реализация по-моему работает точно так же. Насчет второго и третьего замечаний: вы абсолютно правы, спасибо за фидбэк.  - Chaos_Optima14.11.2016 18:03- Работает то также, но дольше и выглядит как хак, собственно говоря им и являясь. 
  - PkXwmpgN14.11.2016 18:10- В качестве рекомендации, по поводу оператора присваивания. 
 Есть такое понятие как каноническая реализация, в основе которого лежит идиома copy-and-swap. У вас правильная идея с временным объектом, просто явная реализация через memcpy сильно "бросается в глаза".
 
  - TheCalligrapher14.11.2016 19:22-3- Ваше предложение о том, как "должен" быть написан оператор присваивания — мимо кассы. 
 - Автор статьи реализовал хорошо известную идиому copy-and-swap (он реализовал ее через - memcpy, что не совсем красиво, но тем не менее). Это — правильно. Да, присваивание надо делать через copy-and-swap.
 - Поэтому не надо нам тут рассказывать про "должен был выглядеть как минимум так". Нет, не должен был.  - Chaos_Optima15.11.2016 12:09- cas хорошая вещь, но тут она вообще не несёт какого либо дополнительного смысла, и добавляет лишних операций. К тому же, для обучающей статьи это явно лишнее. Ну или хотя бы написал что это cas. 
 
 
  - Chaos_Optima14.11.2016 17:43- Пример для третьего случая 
 - int main() { char* test_heap = new char[sizeof(shr_ptr<A>)]; for (int i = 0; i < sizeof(shr_ptr<A>); i++) test_heap[i] = 1; { shr_ptr<A> sh1 = NULL; shr_ptr<A>* sh2 = new(test_heap) shr_ptr<A>(sh1); sh1 = *sh2; delete sh3; } return 0; }
 p.s. не успел отредактировать предыдущий комментарий
 - *count-= 1; if (!count) //вместо *counter -= 1; if (!counter) - kartograph14.11.2016 17:54- Да, я это понял, ошибка действительно явная, буду с ней бороться. Не получиться — спрячу статью в черновики 
 
 
 
 
 
 
 
 
           
 
DimaKurb
как по мне, для начинающих лучше пояснить в чем идея умного указателя и когда его использовать. также пояснить почему и когда умный указатель очистит память тем самым избежав утечки, ИМХО.