Доказанная техника (которая работает и в C) для отделения интерфейса от реализации заключается в использовании указателя на неполный класс в качестве непрозрачной ручки:
Этот метод основан на способности<shared_ptr>выполнять пользовательский удалитель, устраняя явный вызов<fclose>и на том факте, что<shared_ptr<X>>может быть скопирован и уничтожен, когда<X>неполный.
Специфической вариацией 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>составляет<CopyConstructible>и<Assignable>, что позволяет использовать его в стандартных контейнерах.
Другой широко используемой идиомой C++ для разделения inteface и реализации является использование абстрактных базовых классов и фабричных функций. Абстрактные классы иногда называют «интерфейсами», а шаблон известен как «интерфейсное программирование». Опять же,<shared_ptr>можно использовать в качестве возвратного типа фабричных функций:
Ключевое свойство shared_ptr заключается в том, что детали распределения, строительства, размещения и разрушения фиксируются в точке строительства внутри заводской функции. Обратите внимание на защищенный и невиртуальный деструктор в приведенном выше примере. Код клиента не может и не должен удалять указатель на<X>; экземпляр<shared_ptr<X>>, возвращенный из<createX>, правильно вызовет<~X_impl>.
Часто желательно, чтобы клиентский код не удалял указатель, которым управляет<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;
}
};
Обратите внимание, однако, что<shared_array>часто предпочтительнее, если это вариант. Он имеет интерфейс, специфичный для массива, без<operator*>и<operator->>и не допускает преобразования указателей.
<shared_ptr>может использоваться для создания оберток C++ по сравнению с существующими интерфейсами библиотеки стилей C, которые возвращают исходные указатели из их заводских функций для инкапсуляции деталей распределения. В качестве примера рассмотрим этот интерфейс, где<CreateX>может выделить<X>из собственной частной кучи,<~X>может быть недоступным, или<X>может быть неполным:
X * CreateX();
void DestroyX(X *);
Единственный способ надежно уничтожить указатель, возвращенный<CreateX>, - это позвонить<DestroyX>.
Вот как может выглядеть обертка на основе<shared_ptr>:
Иногда желательно создать<shared_ptr>для уже существующего объекта, чтобы<shared_ptr>не пытался уничтожить объект, когда больше не осталось ссылок. Например, функция завода:
shared_ptr<X> createX();
В определенных ситуациях может потребоваться возврат указателя в статически выделенный<X>экземпляр.
Решение состоит в том, чтобы использовать пользовательский удалитель, который ничего не делает:
Справочная информация: Объекты COM имеют встроенный счетчик ссылок и две функции-члены, которые им манипулируют.<AddRef()>Прибавить счет.<Release()>сокращает счёт и разрушает себя, когда счёт падает до нуля.
Можно держать указатель на объект COM в<shared_ptr>:
Обратите внимание, однако, что<shared_ptr>копий, созданных из<pw>, не будут «зарегистрироваться» во встроенном подсчете объекта COM; они будут совместно использовать единую ссылку, созданную в<make_shared_from_COM>. Слабые указатели, созданные из<pw>, будут признаны недействительными, когда последний<shared_ptr>будет уничтожен, независимо от того, жив ли сам объект COM.
Это обобщение вышеуказанной техники. В примере предполагается, что объект выполняет две функции, требуемые<intrusive_ptr>,<intrusive_ptr_add_ref>и<intrusive_ptr_release>:
Одна из целей дизайна<shared_ptr>должна быть использована в библиотечных интерфейсах. Можно столкнуться с ситуацией, когда библиотека принимает аргумент<shared_ptr>, но объектом, находящимся под рукой, управляет другой подсчитанный или связанный интеллектуальный указатель.
Можно использовать пользовательскую функцию удаления<shared_ptr>, чтобы обернуть этот существующий интеллектуальный указатель за фасадом<shared_ptr>:
Одно тонкое замечание заключается в том, что удаляющим не разрешается бросать исключения, и приведенный выше пример в письменном виде предполагает, что<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
}
}
Иногда необходимо получить<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>, никогда не переживет объект, используйтенулевой удалитель.
Некоторые проекты требуют, чтобы объекты регистрировались на строительстве в центральном органе. Когда процедуры регистрации принимают файл 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;
}
};
Иногда требуется получить<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>:
Библиотека теперь включает шаблон класса помощников<
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>заботится об этом.
Вместо необработанного указателя можно использовать<shared_ptr>в качестве ручки и бесплатно получить подсчёт ссылок и автоматическое управление ресурсами:
Это свойство может быть использовано во многом так же, как сырое<void*>используется для временного удаления информации типа из указателя объекта. А<shared_ptr<void>>может быть позже отброшен обратно к правильному типу, используя<
static_pointer_cast>.
<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;
Иногда необходимо вернуть блокировку 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>:
<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();
}
В некоторых ситуациях один<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
Еще одно изменение заключается в переносе логики свободного списка в точку построения с помощью отложенного удаления:
Статья Smart Pointer Programming Techniques раздела может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.