Как использовать "умные" указатели в C++
Что это такое?
Умные указатели, объекты, которые выглядят и чувствуют себя, как указатели, но они гораздо умнее. Что это значит?
Выглядеть и чувствовать себя как указатели, интеллектуальные указатели должны иметь тот же интерфейс, что и обычные указатели: они должны поддерживать, как операции разыменовании (оператор *) так и косвенного (оператор ->).
Чтобы быть умнее, чем обычные указатели, умным указателям необходимо делать то, что 
обычные указатели делать не умеют. Что это будет? Вероятно, наиболее распространенные ошибки в C + + связанные с указателями и управления памятью это: нулевые указатели, утечка памяти, ошибки распределения и другими радостями. Используя умные указатели можно не волноваться о этих вещах.
Простейшим примером умного указателя является auto_ptr, который входит в стандартный C + + библиотеки. Вот реализации auto_ptr: 
template <class T> class auto_ptr
{
    T* ptr;
public:
    explicit auto_ptr(T* p = 0) : ptr(p) {}
    ~auto_ptr()                 {delete ptr;}
    T& operator*()              {return *ptr;}
    T* operator->()             {return ptr;}
    // ...
};
Как вы можете видеть, auto_ptr простая обертка вокруг простого указателя. Она направляет все операции по этому указателю (разыменования и косвенной). А деструктор заботится об удалении указателя.
Это означает, что вместо того чтобы писать:
void foo()
{
    MyClass* p(new MyClass);
    p->DoSomething();
    delete p;
}
Вы можете написать:
void foo()
{
    auto_ptr<MyClass> p(new MyClass);
    p->DoSomething();
}
Зачем мне их использовать? 
Очевидно, что различные умные указатели предлагают различные причины для  их использования. Вот некоторые общие причины для использования в C + +.
Почему меньше ошибок?
Автоматическая очистка. Как показано в коде выше, с использованием умных указателей, которые после себя очищают память, можно сэкономить несколько строк кода. Значение здесь имеет не только количество нажатых клавиш, а еще и снижении вероятности ошибки: вам не нужно заботится о освобождении указателя, и поэтому нет вероятности, что вы забудете об этом.
Автоматическая инициализация. Еще одна приятная вещь в том, что вам не нужно для инициализации auto_ptr присваивать NULL, так как по умолчанию конструктор сделает это за вас.
Висящие указатели. Указатель, который указывает на объект, который уже удален. Следующий код иллюстрирует эту ситуацию:
MyClass* p(new MyClass);
MyClass* q = p;
delete p;
p->DoSomething();   // Осторожно! p уже висит!
p = NULL;           // p уже не висит
q->DoSomething();   // q по прежнему висит!
Для auto_ptr, проблема решается путем установки указателя в NULL, когда он будет скопирован:
template <class T>
auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs)
{
    if (this != &rhs) {
        delete ptr;
        ptr = rhs.ptr;
        rhs.ptr = NULL;
    }
    return *this;
}
Другое умные указатели могут делать другие вещи, когда копируются. Вот некоторые из возможных:
1.Создать новую копию объекта
template <class X> class copied_ptr
{
public:
    typedef X element_type;
    explicit copied_ptr(X* p = 0) throw()       : itsPtr(p) {}
    ~copied_ptr()                               {delete itsPtr;}
    copied_ptr(const copied_ptr& r)             {copy(r.get());}
    copied_ptr& operator=(const copied_ptr& r)
    {
        if (this != &r) {
            delete itsPtr;
            copy(r);
        }
        return *this;
    }
    X& operator*()  const throw()               {return *itsPtr;}
    X* operator->() const throw()               {return itsPtr;}
    X* get()        const throw()               {return itsPtr;}
private:
    X* itsPtr;
    void copy(const copied_ptr& r)  {itsPtr = r.itsPtr ? new X(*r.itsPtr) : 0;}
};
2.Передать права собственности
3.Подсчет ссылок. Поддержание количества умных указателей, которые указывают на тот же объект, и удаление объекта при этом количество станет равным нулю.
template <class X> class counted_ptr
{
public:
    typedef X element_type;
    explicit counted_ptr(X* p = 0) // allocate a new counter
        : itsCounter(0) {if (p) itsCounter = new counter(p);}
    ~counted_ptr()
        {release();}
    counted_ptr(const counted_ptr& r) throw()
        {acquire(r.itsCounter);}
    counted_ptr& operator=(const counted_ptr& r)
    {
        if (this != &r) {
            release();
            acquire(r.itsCounter);
        }
        return *this;
    }
#ifndef NO_MEMBER_TEMPLATES
    template <class Y> friend class counted_ptr<Y>;
    template <class Y> counted_ptr(const counted_ptr<Y>& r) throw()
        {acquire(r.itsCounter);}
    template <class Y> counted_ptr& operator=(const counted_ptr<Y>& r)
    {
        if (this != &r) {
            release();
            acquire(r.itsCounter);
        }
        return *this;
    }
#endif // NO_MEMBER_TEMPLATES
    X& operator*()  const throw()   {return *itsCounter->ptr;}
    X* operator->() const throw()   {return itsCounter->ptr;}
    X* get()        const throw()   {return itsCounter ? itsCounter->ptr : 0;}
    bool unique()   const throw()
        {return (itsCounter ? itsCounter->count == 1 : true);}
private:
    struct counter {
        counter(X* p = 0, unsigned c = 1) : ptr(p), count(c) {}
        X*          ptr;
        unsigned    count;
    }* itsCounter;
    void acquire(counter* c) throw()
    { // increment the count
        itsCounter = c;
        if (c) ++c->count;
    }
    void release()
    { // decrement the count, delete if it is 0
        if (itsCounter) {
            if (--itsCounter->count == 0) {
                delete itsCounter->ptr;
                delete itsCounter;
            }
            itsCounter = 0;
        }
    }
};
Все эти технологии помогают в борьбе с проблемами висящих указателей.
Почему: Exception безопасности 
Давайте еще раз взглянуть на этот простой пример:
void foo()
{
    MyClass* p(new MyClass);
    p->DoSomething();
    delete p;
}
Что произойдет, если DoSomething () произведет исключение? Все строки после него не будут выполнены и р никогда не удалится! Если нам повезет, то это приводит только к утечке памяти. Однако, в деструкторе MyClass могут освобождаться другие ресурсы (дескрипторы файлов, потоки, COM ссылки, мьютексы).
Если использовать умные указатели, деструктор будет вызван, во время выхода с функции, будь то в ходе нормального выполнения или во время исключения.
Но разве это не возможно, написать обработку исключений обычными указателями? Конечно, но я сомневаюсь, что кто-то будет считать это нормальной альтернативой. Вот что вы могли бы сделать в данном случае:
void foo()
{
    MyClass* p;
    try {
        p = new MyClass;
        p->DoSomething();
        delete p;
    }
    catch (...) {
        delete p;
        throw;
    }
}
Почему сборка мусора?
Поскольку C + + не обеспечивает автоматический сбор мусора, как и некоторые другие Языки, умные указатели могут быть использованы для этой цели. Простейшая схема сборки мусора заключается в подсчете ссылок, но вполне возможно реализовать более сложный сбор мусора с умными указателями.
Почему эффективность?
Умные указатели могут быть использованы для более эффективного использования имеющейся памяти и сократить время выделение и освобождение.
Общепринятая стратегия использования памяти более эффективно копирования при записи (COW - copy on write). Это означает, что один и тот же объект, разделяют многие указатели до тех пор, пока объект только читали и не изменяли. Когда какая-то часть программы пытается изменить объект ( "Write"), указатель COW создает новую копию объекта и изменяет эту копию вместо оригинального объекта. Стандартные классы строки обычно реализованы с использованием COW:
string s("Hello");
string t = s;       // t и s указывают на один и тот же буфер символов
t += " there!";     // Новый буфер выделяется для т с
                    // добавлением " there!", так что s является неизменнен.
STL контейнеры 
C + + стандартная библиотека включает в себя набор контейнеров и алгоритмов, известных в качестве стандартной библиотеки шаблонов (STL - standard template library). STL это универсальность (можно использовать с любым объектом) и эффективность (больше скорость по сравнению с альтернативами). Для достижения этих двух целей, STL контейнеры хранят свои объекты по значению. Это означает, что если у вас есть STL контейнер, который хранит объекты базового класса, он не может хранить объекты производных классов.
class Base { /*...*/ };
class Derived : public Base { /*...*/ };
Base b;
Derived d;
vector<Base> v;
v.push_back(b); // хорошо
v.push_back(d); // ошибка
Что делать, если вам нужен набор объектов из разных классов? Простейшим решением является коллекция указателей:
vector<Base*> v;
v.push_back(new Base);      // хорошо
v.push_back(new Derived);   // тоже хорошо
// очистка:
for (vector<Base*>::iterator i = v.begin(); i != v.end(); ++i)
    delete *i;
Проблема этого решения, в том, что после работы с контейнером, вам нужно вручную удалять объекты, хранящиеся в нем. И потому этот способ подвержен ошибкам.
Возможно решение проблемы с использованием умных указателей:
vector<linked_ptr<Base> > v;
v.push_back(new Base);      // OK
v.push_back(new Derived);   // OK too
// cleanup is automatic
Так как умные указатели автоматически чистит после себя, нет необходимости удалять вручную.
Заключение 
Умные указатели являются полезным инструментом для написания безопасного и эффективного кода на C++. Как и любой инструмент, они должны использоваться с соответствующей осторожностью. 
Библиотека Boost C++ включает умные указатели, которые более тщательно протестированы. В первую очередь рекомендую использовать их, если они подходят для ваших нужд.

отличная статья
ОтветитьУдалитьСпасибо большое. Осталось найти где лежит linked_ptr.
ОтветитьУдалить