Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Smart Pointer Programming Techniques

Boost , ,

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

Smart Pointer Programming Techniques

Использование неполных классов для реализации, скрывающих
Использование абстрактных классов для реализации, скрывающих
Предотвращение<delete px.get()>
Использование<shared_ptr>для удержания указателя на массив
Инкапсулирование деталей распределения, обертывание заводских функций
Использование<shared_ptr>для удержания указателя на статически выделенный объект
Использование<shared_ptr>для удержания указателя на объект COM
Использование<shared_ptr>для удержания указателя на объект со встроенным эталонным счетчиком
Использование<shared_ptr>для удержания другого совместного владения умным указателем
Получение<shared_ptr>от необработанного указателя
до<this>в конструкторе
Получение<shared_ptr>до<this>[20Использование<shared_ptr>в качестве умной счетной ручки
Использование<shared_ptr>для выполнения кода на выходе блока
Использование<shared_ptr<void>>для удержания произвольного объекта
Связывание произвольных данных с гетерогенными<shared_ptr>Примеры
Использование<shared_ptr>как CopyConstructible mutex lock
Использование<shared_ptr>для обертывания вызовов функции участника
Задержка размещения
Слабые указатели на объекты, не управляемые<shared_ptr>

Using incomplete classes for implementation hiding

Доказанная техника (которая работает и в C) для отделения интерфейса от реализации заключается в использовании указателя на неполный класс в качестве непрозрачной ручки:

class FILE;
FILE * fopen(char const * name, char const * mode);
void fread(FILE * f, void * data, size_t size);
void fclose(FILE * f);

Выразить вышеописанный интерфейс можно с помощью<shared_ptr>, исключив необходимость вызова вручную<fclose>:

class FILE;
shared_ptr<FILE> fopen(char const * name, char const * mode);
void fread(shared_ptr<FILE> f, void * data, size_t size);

Этот метод основан на способности<shared_ptr>выполнять пользовательский удалитель, устраняя явный вызов<fclose>и на том факте, что<shared_ptr<X>>может быть скопирован и уничтожен, когда<X>неполный.

The "Pimpl" idiom

Специфической вариацией C++ неполного шаблона класса является идиома «Pimpl». Неполный класс не подвергается воздействию пользователя; он скрыт за пересылочным фасадом.<shared_ptr>может быть использовано для реализации «Утилиты»:

// file.hpp:
class file
{
private:
    class impl;
    shared_ptr<impl> pimpl_;
public:
    file(char const * name, char const * mode);
    // compiler generated members are fine and useful
    void read(void * data, size_t size);
};
// file.cpp:
#include "file.hpp"
class file::impl
{
private:
    impl(impl const &);
    impl & operator=(impl const &);
    // private data
public:
    impl(char const * name, char const * mode) { ... }
    ~impl() { ... }
    void read(void * data, size_t size) { ... }
};
file::file(char const * name, char const * mode): pimpl_(new impl(name, mode))
{
}
void file::read(void * data, size_t size)
{
    pimpl_->read(data, size);
}

Ключевым моментом здесь является то, что созданный компилятором конструктор копий, оператор присваивания и деструктор имеют разумное значение. В результате< file>составляет<CopyConstructible>и<Assignable>, что позволяет использовать его в стандартных контейнерах.

Using abstract classes for implementation hiding

Другой широко используемой идиомой C++ для разделения inteface и реализации является использование абстрактных базовых классов и фабричных функций. Абстрактные классы иногда называют «интерфейсами», а шаблон известен как «интерфейсное программирование». Опять же,<shared_ptr>можно использовать в качестве возвратного типа фабричных функций:

// X.hpp:
class X
{
public:
    virtual void f() = 0;
    virtual void g() = 0;
protected:
    ~X() {}
};
shared_ptr<X> createX();
-- X.cpp:
class X_impl: public X
{
private:
    X_impl(X_impl const &);
    X_impl & operator=(X_impl const &);
public:
    virtual void f()
    {
      // ...
    }
    virtual void g()
    {
      // ...
    }
};
shared_ptr<X> createX()
{
    shared_ptr<X> px(new X_impl);
    return px;
}

Ключевое свойство shared_ptr заключается в том, что детали распределения, строительства, размещения и разрушения фиксируются в точке строительства внутри заводской функции. Обратите внимание на защищенный и невиртуальный деструктор в приведенном выше примере. Код клиента не может и не должен удалять указатель на<X>; экземпляр<shared_ptr<X>>, возвращенный из<createX>, правильно вызовет<~X_impl>.

Preventing delete px.get()

Часто желательно, чтобы клиентский код не удалял указатель, которым управляет<shared_ptr>. Предыдущая методика показала один возможный подход, используя защищенный деструктор. Другой альтернативой является использование частного удаления:

class X
{
private:
    ~X();
    class deleter;
    friend class deleter;
    class deleter
    {
    public:
        void operator()(X * p) { delete p; }
    };
public:
    static shared_ptr<X> create()
    {
        shared_ptr<X> px(new X, X::deleter());
        return px;
    }
};

Using a shared_ptr to hold a pointer to an array

<shared_ptr>может использоваться для удержания указателя на массив, выделенный<new[]>:

shared_ptr<X> px(new X[1], checked_array_deleter<X>());

Обратите внимание, однако, что<shared_array>часто предпочтительнее, если это вариант. Он имеет интерфейс, специфичный для массива, без<operator*>и<operator->>и не допускает преобразования указателей.

Encapsulating allocation details, wrapping factory functions

<shared_ptr>может использоваться для создания оберток C++ по сравнению с существующими интерфейсами библиотеки стилей C, которые возвращают исходные указатели из их заводских функций для инкапсуляции деталей распределения. В качестве примера рассмотрим этот интерфейс, где<CreateX>может выделить<X>из собственной частной кучи,<~X>может быть недоступным, или<X>может быть неполным:

X * CreateX();
void DestroyX(X *);

Единственный способ надежно уничтожить указатель, возвращенный<CreateX>, - это позвонить<DestroyX>.

Вот как может выглядеть обертка на основе<shared_ptr>:

shared_ptr<X> createX()
{
    shared_ptr<X> px(CreateX(), DestroyX);
    return px;
}

Клиентскому коду, который вызывает<createX>, все равно не нужно знать, как был выделен объект, но теперь уничтожение происходит автоматически.

Using a shared_ptr to hold a pointer to a statically allocated object

Иногда желательно создать<shared_ptr>для уже существующего объекта, чтобы<shared_ptr>не пытался уничтожить объект, когда больше не осталось ссылок. Например, функция завода:

shared_ptr<X> createX();

В определенных ситуациях может потребоваться возврат указателя в статически выделенный<X>экземпляр.

Решение состоит в том, чтобы использовать пользовательский удалитель, который ничего не делает:

struct null_deleter
{
    void operator()(void const *) const
    {
    }
};
static X x;
shared_ptr<X> createX()
{
    shared_ptr<X> px(&x, null_deleter());
    return px;
}

Та же техника работает для любого объекта, который, как известно, переживает указатель.

Using a shared_ptr to hold a pointer to a COM Object

Справочная информация: Объекты COM имеют встроенный счетчик ссылок и две функции-члены, которые им манипулируют.<AddRef()>Прибавить счет.<Release()>сокращает счёт и разрушает себя, когда счёт падает до нуля.

Можно держать указатель на объект COM в<shared_ptr>:

shared_ptr<IWhatever> make_shared_from_COM(IWhatever * p)
{
    p->AddRef();
    shared_ptr<IWhatever> pw(p, mem_fn(&IWhatever::Release));
    return pw;
}

Обратите внимание, однако, что<shared_ptr>копий, созданных из<pw>, не будут «зарегистрироваться» во встроенном подсчете объекта COM; они будут совместно использовать единую ссылку, созданную в<make_shared_from_COM>. Слабые указатели, созданные из<pw>, будут признаны недействительными, когда последний<shared_ptr>будет уничтожен, независимо от того, жив ли сам объект COM.

Какобъяснилв<mem_fn>документации, вам нужноопределить BOOST_MEM_FN_ENABLE_STDCALLПервый.

Using a shared_ptr to hold a pointer to an object with an embedded reference count

Это обобщение вышеуказанной техники. В примере предполагается, что объект выполняет две функции, требуемые<intrusive_ptr>,<intrusive_ptr_add_ref>и<intrusive_ptr_release>:

template<class T> struct intrusive_deleter
{
    void operator()(T * p)
    {
        if(p) intrusive_ptr_release(p);
    }
};
shared_ptr<X> make_shared_from_intrusive(X * p)
{
    if(p) intrusive_ptr_add_ref(p);
    shared_ptr<X> px(p, intrusive_deleter<X>());
    return px;
}

Using a shared_ptr to hold another shared ownership smart pointer

Одна из целей дизайна<shared_ptr>должна быть использована в библиотечных интерфейсах. Можно столкнуться с ситуацией, когда библиотека принимает аргумент<shared_ptr>, но объектом, находящимся под рукой, управляет другой подсчитанный или связанный интеллектуальный указатель.

Можно использовать пользовательскую функцию удаления<shared_ptr>, чтобы обернуть этот существующий интеллектуальный указатель за фасадом<shared_ptr>:

template<class P> struct smart_pointer_deleter
{
private:
    P p_;
public:
    smart_pointer_deleter(P const & p): p_(p)
    {
    }
    void operator()(void const *)
    {
        p_.reset();
    }
    
    P const & get() const
    {
        return p_;
    }
};
shared_ptr<X> make_shared_from_another(another_ptr<X> qx)
{
    shared_ptr<X> px(qx.get(), smart_pointer_deleter< another_ptr<X> >(qx));
    return px;
}

Одно тонкое замечание заключается в том, что удаляющим не разрешается бросать исключения, и приведенный выше пример в письменном виде предполагает, что<p_.reset()>не бросает. Если это не так,<p_.reset()>должен быть завернут в блок<try {} catch(...) {}>, который игнорирует исключения. В (обычно маловероятном) случае, когда исключение выбрасывается и игнорируется,<p_>будет выпущен, когда срок службы удаляющего устройства заканчивается. Это происходит, когда все ссылки, включая слабые указатели, уничтожаются или сбрасываются.

Другой поворот заключается в том, что, учитывая приведенный выше пример<shared_ptr>, можно восстановить исходный интеллектуальный указатель, используя< get_deleter>:

void extract_another_from_shared(shared_ptr<X> px)
{
    typedef smart_pointer_deleter< another_ptr<X> > deleter;
    if(deleter const * pd = get_deleter<deleter>(px))
    {
        another_ptr<X> qx = pd->get();
    }
    else
    {
        // not one of ours
    }
}

Obtaining a shared_ptr from a raw pointer

Иногда необходимо получить<shared_ptr>заданный необработанный указатель на объект, который уже управляется другим<shared_ptr>экземпляром. Пример:

void f(X * p)
{
    shared_ptr<X> px(???);
}

В<f>мы хотели бы создать<shared_ptr>к<*p>.

В общем случае эта проблема не имеет решения. Один из подходов состоит в том, чтобы изменить<f>, чтобы принять<shared_ptr>, если это возможно:

void f(shared_ptr<X> px);

То же самое преобразование может быть использовано для невиртуальных функций члена, чтобы преобразовать неявное<this>:

void X::f(int m);

Станет свободной функцией с первым аргументом<shared_ptr>:

void f(shared_ptr<X> this_, int m);

Если<f>не может быть изменено, но<X>использует интрузивный подсчет, используйте<make_shared_from_intrusive>, описанный выше. Или, если известно, что<shared_ptr>, созданный в<f>, никогда не переживет объект, используйтенулевой удалитель.

Obtaining a shared_ptr (weak_ptr) to this in a constructor

Некоторые проекты требуют, чтобы объекты регистрировались на строительстве в центральном органе. Когда процедуры регистрации принимают файл share_ptr, возникает вопрос, как конструктор может получить файл share_ptr:

class X
{
public:
    X()
    {
        shared_ptr<X> this_(???);
    }
};

В общем случае проблему решить невозможно. Сконструированный экземпляр<X>может быть автоматической переменной или статической переменной; он может быть создан на куче:

shared_ptr<X> px(new X);

Но на момент строительства<px>еще не существует, и невозможно создать другой<shared_ptr>экземпляр, который разделяет с ним право собственности.

В зависимости от контекста, если внутренний<shared_ptr><this_>не нуждается в сохранении объекта живым, используйте<null_deleter>, как объясненоздесьиздесь. Если предполагается, что<X>всегда живет на куче и управляется<shared_ptr>, используйте статичную фабричную функцию:

class X
{
private:
    X() { ... }
public:
    static shared_ptr<X> create()
    {
        shared_ptr<X> px(new X);
        // use px as 'this_'
        return px;
    }
};

Obtaining a shared_ptr to this

Иногда требуется получить<shared_ptr>из<this>в виртуальной функции-члене при условии, что<this>уже управляется<shared_ptr>. Преобразования, описанные в предыдущем методе, не могут быть применены.

Типичный пример:

class X
{
public:
    virtual void f() = 0;
protected:
    ~X() {}
};
class Y
{
public:
    virtual shared_ptr<X> getX() = 0;
protected:
    ~Y() {}
};
// --
class impl: public X, public Y
{
public:
    impl() { ... }
    virtual void f() { ... }
    virtual shared_ptr<X> getX()
    {
        shared_ptr<X> px(???);
        return px;
    }
};

Решение состоит в том, чтобы сохранить слабый указатель<this>в качестве члена<impl>:

class impl: public X, public Y
{
private:
    weak_ptr<impl> weak_this;
    impl(impl const &);
    impl & operator=(impl const &);
    impl() { ... }
public:
    static shared_ptr<impl> create()
    {
        shared_ptr<impl> pi(new impl);
        pi->weak_this = pi;
        return pi;
    }
    virtual void f() { ... }
    virtual shared_ptr<X> getX()
    {
        shared_ptr<X> px(weak_this);
        return px;
    }
};

Библиотека теперь включает шаблон класса помощников< enable_shared_from_this>, который можно использовать для инкапсуляции решения:

class impl: public X, public Y, public enable_shared_from_this<impl>
{
public:
    impl(impl const &);
    impl & operator=(impl const &);
public:
    virtual void f() { ... }
    virtual shared_ptr<X> getX()
    {
        return shared_from_this();
    }
}

Обратите внимание, что вам больше не нужно вручную инициализировать элемент<weak_ptr>в<enable_shared_from_this>. Построение<shared_ptr>до<impl>заботится об этом.

Using shared_ptr as a smart counted handle

Некоторые библиотечные интерфейсы используют непрозрачные ручки, вариацию описанной выше методикинеполного класса. Пример:

typedef void * HANDLE;
HANDLE CreateProcess();
void CloseHandle(HANDLE);

Вместо необработанного указателя можно использовать<shared_ptr>в качестве ручки и бесплатно получить подсчёт ссылок и автоматическое управление ресурсами:

typedef shared_ptr<void> handle;
handle createProcess()
{
    shared_ptr<void> pv(CreateProcess(), CloseHandle);
    return pv;
}

Using shared_ptr to execute code on block exit

<shared_ptr<void>>может автоматически выполнять код очистки, когда управление покидает область действия.

  • Executing f(p), where p is a pointer:
    shared_ptr<void> guard(p, f);
  • Executing arbitrary code: f(x, y):
    shared_ptr<void> guard(static_cast<void*>(0), bind(f, x, y));

Для более тщательного лечения см. статью «Упростите свой код исключения-безопасности» Андрея Александреску и Петру Маргинян, доступную онлайн по адресуhttp://www.cuj.com/experts/1812/alexandr.htm?topic=experts.

Using shared_ptr<void> to hold an arbitrary object

<shared_ptr<void>>может выступать в качестве общего указателя объекта, аналогичного<void*>. Когда<shared_ptr<void>>экземпляр построен как:

    shared_ptr<void> pv(new X);

Уничтожается, правильно утилизирует<X>объект, выполняя<~X>.

Это свойство может быть использовано во многом так же, как сырое<void*>используется для временного удаления информации типа из указателя объекта. А<shared_ptr<void>>может быть позже отброшен обратно к правильному типу, используя< static_pointer_cast>.

Associating arbitrary data with heterogeneous shared_ptr instances

<shared_ptr>и<weak_ptr>поддерживают<operator<>сравнения, требуемые стандартными ассоциативными контейнерами, такими как<std::map>. Это можно использовать для ненавязчивой связи произвольных данных с объектами, управляемыми<shared_ptr>:

typedef int Data;
std::map< shared_ptr<void>, Data > userData;
// or std::map< weak_ptr<void>, Data > userData; to not affect the lifetime
shared_ptr<X> px(new X);
shared_ptr<int> pi(new int(3));
userData[px] = 42;
userData[pi] = 91;

Using shared_ptr as a CopyConstructible mutex lock

Иногда необходимо вернуть блокировку mutex из функции, а некопируемый замок не может быть возвращен по значению. Можно использовать<shared_ptr>в качестве блокировки мутекса:

class mutex
{
public:
    void lock();
    void unlock();
};
shared_ptr<mutex> lock(mutex & m)
{
    m.lock();
    return shared_ptr<mutex>(&m, mem_fn(&mutex::unlock));
}

Более того, экземпляр<shared_ptr>, действующий как замок, может быть инкапсулирован в выделенном классе<shared_lock>:

class shared_lock
{
private:
    shared_ptr<void> pv;
public:
    template<class Mutex> explicit shared_lock(Mutex & m): pv((m.lock(), &m), mem_fn(&Mutex::unlock)) {}
};

<shared_lock>можно использовать как:

    shared_lock lock(m);

Обратите внимание, что<shared_lock>не является шаблоном типа mutex, благодаря способности< shared_ptr<void>>скрывать информацию о типе.

Using shared_ptr to wrap member function calls

<shared_ptr>реализует семантику владения, требуемую из схемы<Wrap>/<CallProxy>, описанной в статье Бьярна Страуструпа «Обертывание вызовов функции члена C++» (доступно онлайн по адресу). http://www.stroustrup.com/wrapper.pdf. Осуществление приводится ниже:

template<class T> class pointer
{
private:
    T * p_;
public:
    explicit pointer(T * p): p_(p)
    {
    }
    shared_ptr<T> operator->() const
    {
        p_->prefix();
        return shared_ptr<T>(p_, mem_fn(&T::suffix));
    }
};
class X
{
private:
    void prefix();
    void suffix();
    friend class pointer<X>;
    
public:
    void f();
    void g();
};
int main()
{
    X x;
    pointer<X> px(&x);
    px->f();
    px->g();
}

Delayed deallocation

В некоторых ситуациях один<px.reset()>может вызвать дорогостоящее распределение сделок в критически важной для производительности области:

class X; // ~X is expensive
class Y
{
    shared_ptr<X> px;
public:
    void f()
    {
        px.reset();
    }
};

Решение состоит в том, чтобы отложить потенциальное распределение сделок, переместив<px>в специальный бесплатный список, который может периодически опустошаться, когда производительность и время отклика не являются проблемой:

vector< shared_ptr<void> > free_list;
class Y
{
    shared_ptr<X> px;
public:
    void f()
    {
        free_list.push_back(px);
        px.reset();
    }
};
// periodically invoke free_list.clear() when convenient

Еще одно изменение заключается в переносе логики свободного списка в точку построения с помощью отложенного удаления:

struct delayed_deleter
{
    template<class T> void operator()(T * p)
    {
        try
        {
            shared_ptr<void> pv(p);
            free_list.push_back(pv);
        }
        catch(...)
        {
        }
    }
};

Weak pointers to objects not managed by a shared_ptr

Заставьте объект удерживать<shared_ptr>, используя<null_deleter>:

class X
{
private:
    shared_ptr<X> this_;
    int i_;
public:
    explicit X(int i): this_(this, null_deleter()), i_(i)
    {
    }
    // repeat in all constructors (including the copy constructor!)
    X(X const & rhs): this_(this, null_deleter()), i_(rhs.i_)
    {
    }
    // do not forget to not assign this_ in the copy assignment
    X & operator=(X const & rhs)
    {
        i_ = rhs.i_;
    }
    weak_ptr<X> get_weak_ptr() const { return this_; }
};

Когда срок службы объекта закончится,<X::this_>будет уничтожен, и все слабые точки автоматически истечет.


$Date$

Авторское право и копия; 2003 Питер Димов. Распространяется под лицензией Boost Software License, версия 1.0. См. сопроводительный файлLICENSE_1_0.txtили копию наhttp://www.boost.org/LICENSE_1_0.txt

Статья Smart Pointer Programming Techniques раздела может быть полезна для разработчиков на c++ и boost.




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.



:: Главная :: ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-20 04:46:27/0.030018091201782/1