Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Тип shared_ptr
— это умный указатель в стандартной библиотеке C++, предназначенной для сценариев, в которых несколько владельцев должны управлять временем существования объекта. После инициализации указателя shared_ptr
его можно копировать, передавать по значению в аргументах функций и присваивать другим экземплярам shared_ptr
. Все экземпляры указывают на один и тот же объект и имеют общий доступ к одному "блоку управления", который увеличивает и уменьшает счетчик ссылок, когда указатель shared_ptr
добавляется, выходит из области действия или сбрасывается. Когда счетчик ссылок достигает нуля, блок управления удаляет ресурс в памяти и самого себя.
На схеме ниже показано несколько экземпляров shared_ptr
, указывающих на одно расположение в памяти.
На первой схеме показан общий указатель P1, указывающий на экземпляр MyClass, а также блок управления с числом ссылок = 1. На второй схеме показано добавление другого общего указателя P2, который также указывает на экземпляр MyClass и общий блок управления, который теперь имеет число ссылок 2.
Пример настройки
Во всех примерах далее предполагается, что вы включили необходимые заголовки и объявили необходимые типы следующим образом:
// shared_ptr-examples.cpp
// The following examples assume these declarations:
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct MediaAsset
{
virtual ~MediaAsset() = default; // make it polymorphic
};
struct Song : public MediaAsset
{
std::wstring artist;
std::wstring title;
Song(const std::wstring& artist_, const std::wstring& title_) :
artist{ artist_ }, title{ title_ } {}
};
struct Photo : public MediaAsset
{
std::wstring date;
std::wstring location;
std::wstring subject;
Photo(
const std::wstring& date_,
const std::wstring& location_,
const std::wstring& subject_) :
date{ date_ }, location{ location_ }, subject{ subject_ } {}
};
using namespace std;
int main()
{
// The examples go here, in order:
// Example 1
// Example 2
// Example 3
// Example 4
// Example 6
}
Пример 1
По возможности используйте функцию make_shared
для создания shared_ptr
при создании ресурса памяти в первый раз. Функция make_shared
безопасна в отношении исключений. Для выделения памяти под блок управления и ресурс используется один вызов, что снижает накладные расходы. Если вы не используете функцию make_shared
, то придется использовать явное выражение new
для создания объекта, прежде чем передавать его в конструктор shared_ptr
. В приведенном ниже примере представлены различные способы объявления и инициализации указателя shared_ptr
вместе с новым объектом.
// Use make_shared function when possible.
auto sp1 = make_shared<Song>(L"The Beatles", L"Im Happy Just to Dance With You");
// Ok, but slightly less efficient.
// Note: Using new expression as constructor argument
// creates no named variable for other code to access.
shared_ptr<Song> sp2(new Song(L"Lady Gaga", L"Just Dance"));
// When initialization must be separate from declaration, e.g. class members,
// initialize with nullptr to make your programming intent explicit.
shared_ptr<Song> sp5(nullptr);
//Equivalent to: shared_ptr<Song> sp5;
//...
sp5 = make_shared<Song>(L"Elton John", L"I'm Still Standing");
Пример 2
В следующем примере показано, как объявить и инициализировать shared_ptr
экземпляры, которые принимают совместное владение объектом, выделенным другим shared_ptr
. Предполагается, что sp2
— это инициализированный указатель shared_ptr
.
//Initialize with copy constructor. Increments ref count.
auto sp3(sp2);
//Initialize via assignment. Increments ref count.
auto sp4 = sp2;
//Initialize with nullptr. sp7 is empty.
shared_ptr<Song> sp7(nullptr);
// Initialize with another shared_ptr. sp1 and sp2
// swap pointers as well as ref counts.
sp1.swap(sp2);
Пример 3
Указатели shared_ptr
также полезны при использовании алгоритмов копирования элементов в контейнеры стандартной библиотеки C++. Элемент можно заключить в указатель shared_ptr
, а затем копировать его в другие контейнеры, учитывая при этом, что выделенная область памяти доступна только до тех пор, пока она требуется. В приведенном ниже примере показано, как использовать алгоритм remove_copy_if
применительно к экземплярам shared_ptr
в векторе.
vector<shared_ptr<Song>> v {
make_shared<Song>(L"Bob Dylan", L"The Times They Are A Changing"),
make_shared<Song>(L"Aretha Franklin", L"Bridge Over Troubled Water"),
make_shared<Song>(L"Thalía", L"Entre El Mar y Una Estrella")
};
vector<shared_ptr<Song>> v2;
remove_copy_if(v.begin(), v.end(), back_inserter(v2), [] (shared_ptr<Song> s)
{
return s->artist.compare(L"Bob Dylan") == 0;
});
for (const auto& s : v2)
{
wcout << s->artist << L":" << s->title << endl;
}
Пример 4
Для приведения указателя dynamic_pointer_cast
можно использовать функции static_pointer_cast
, const_pointer_cast
и shared_ptr
. Они похожи на операторы dynamic_cast
, static_cast
и const_cast
. В следующем примере показано, как протестировать производный тип каждого элемента в векторе базовых shared_ptr
классов, а затем копировать элементы и отображать сведения о них.
vector<shared_ptr<MediaAsset>> assets {
make_shared<Song>(L"Himesh Reshammiya", L"Tera Surroor"),
make_shared<Song>(L"Penaz Masani", L"Tu Dil De De"),
make_shared<Photo>(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")
};
vector<shared_ptr<MediaAsset>> photos;
copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr<MediaAsset> p) -> bool
{
// Use dynamic_pointer_cast to test whether
// element is a shared_ptr<Photo>.
shared_ptr<Photo> temp = dynamic_pointer_cast<Photo>(p);
return temp.get() != nullptr;
});
for (const auto& p : photos)
{
// We know that the photos vector contains only
// shared_ptr<Photo> objects, so use static_cast.
wcout << "Photo location: " << (static_pointer_cast<Photo>(p))->location << endl;
}
Пример 5
Указатель shared_ptr
можно передать в другую функцию описанными ниже способами.
Передача
shared_ptr
по значению. При этом вызывается конструктор копий, увеличивается счетчик ссылок, и вызываемый объект становится владельцем. С этой операцией связаны некоторые накладные расходы, которые зависят от количества передаваемых объектовshared_ptr
. Используйте этот вариант, если в соответствии с неявным или явным контрактом кода между вызывающим и вызываемым объектами последний должен быть владельцем.Передача
shared_ptr
по ссылке или константной ссылке. В этом случае счетчик ссылок не увеличивается, а указатель доступен вызываемому объекту, пока вызывающий объект остается в области действия. Вызываемый объект может также создать указательshared_ptr
на основе ссылки и стать общим владельцем. Используйте этот вариант, когда вызываемый объект неизвестен вызывающему или когда необходимо передать указательshared_ptr
, избегая операции копирования из соображений производительности.Передача базового указателя или ссылки на базовый объект. Это позволяет вызываемому использовать объект, но он не разделяет право собственности на объект с вызывающим
shared_ptr
. Остерегайтесь ситуации, когда функция-вызова создаётshared_ptr
из переданного необработанного указателя, потому чтоshared_ptr
у функции-вызова имеет независимое число ссылок отshared_ptr
у вызывающей стороны. Когдаshared_ptr
в вызываемом выходит из области видимости, оно удалит объект, оставляя указатель в "shared_ptr" вызывающего, который указывает на освобожденную память. Когда вызывающийshared_ptr
затем выходит из области, двойные результаты будут бесплатными. Этот параметр используется только в том случае, если контракт между вызывающим и вызываемым явно указывает, что вызывающий сохраняет владение временем существованияshared_ptr
.При выборе способа передачи
shared_ptr
, установите, должна ли вызываемая сторона также быть владельцем базового ресурса. Владелец — это объект или функция, которые могут поддерживать существование базового ресурса до тех пор, пока он нужен. Если вызывающая сторона должна гарантировать, что вызываемая сторона может продлить существование указателя после того, как она перестанет существовать, используйте первый вариант. Если возможность продления времени существования вызываемой стороной не важна, передайте указатель по ссылке, чтобы вызываемая сторона скопировала его или не скопировала.Если необходимо предоставить вспомогательный доступ к базовому указателю, и вы знаете, что вспомогающая функция использует указатель и возвращается перед возвратом вызывающей функции, то эта функция не требует совместного владения базовым указателем. Ей просто необходим доступ к указателю в течение времени существования
shared_ptr
вызывающей стороны. В этом случае можно спокойно передатьshared_ptr
по ссылке либо передать необработанный указатель или ссылку на базовый объект. Это дает небольшой выигрыш в производительности и порой позволяет более ясно выразить назначение кода.Иногда, например в
std::vector<shared_ptr<T>>
, может быть необходимо передать каждый указательshared_ptr
в тело лямбда-выражения или в именованный объект функции. Если в лямбда-выражении или функции указатель не сохраняется, передайтеshared_ptr
по ссылке, чтобы не вызывать конструктор копий для каждого элемента.
void use_shared_ptr_by_value(shared_ptr<int> sp);
void use_shared_ptr_by_reference(shared_ptr<int>& sp);
void use_shared_ptr_by_const_reference(const shared_ptr<int>& sp);
void use_raw_pointer(int* p);
void use_reference(int& r);
void test() {
auto sp = make_shared<int>(5);
// Pass the shared_ptr by value.
// This invokes the copy constructor, increments the reference count, and makes the callee an owner.
use_shared_ptr_by_value(sp);
// Pass the shared_ptr by reference or const reference.
// In this case, the reference count isn't incremented.
use_shared_ptr_by_reference(sp);
use_shared_ptr_by_const_reference(sp);
// Pass the underlying pointer or a reference to the underlying object.
use_raw_pointer(sp.get());
use_reference(*sp);
// Pass the shared_ptr by value.
// This invokes the move constructor, which doesn't increment the reference count
// but in fact transfers ownership to the callee.
use_shared_ptr_by_value(move(sp));
}
Пример 6
В приведенном ниже примере показано, как shared_ptr
перегружает различные операторы сравнения, чтобы обеспечить сравнение указателей в памяти, принадлежащей экземплярам shared_ptr
.
// Initialize two separate raw pointers.
// Note that they contain the same values.
auto song1 = new Song(L"Village People", L"YMCA");
auto song2 = new Song(L"Village People", L"YMCA");
// Create two unrelated shared_ptrs.
shared_ptr<Song> p1(song1);
shared_ptr<Song> p2(song2);
// Unrelated shared_ptrs are never equal.
wcout << "p1 < p2 = " << std::boolalpha << (p1 < p2) << endl;
wcout << "p1 == p2 = " << std::boolalpha <<(p1 == p2) << endl;
// Related shared_ptr instances are always equal.
shared_ptr<Song> p3(p2);
wcout << "p3 == p2 = " << std::boolalpha << (p3 == p2) << endl;