std::shared_ptr
— это интеллектуальный указатель, который сохраняет совместное владение объектом через указатель. Объект удаляется, когда последний оставшийся общий указатель на него выходит за пределы области действия.
Эта реализация предназначена для демонстрации основных функций std::shared_ptr
, но не является полной реализацией и не предназначена для использования в производственном коде.
template<typename T> class shared_ptr { template <typename U> friend class shared_ptr; public: explicit shared_ptr(T* ptr = nullptr) : ptr_(ptr), count_(new std::atomic<std::size_t>(1)) { } ~shared_ptr() { if (--*count_ == 0) { delete ptr_; delete count_; } } shared_ptr(const shared_ptr& other) : ptr_(other.ptr_), count_(other.count_) { ++*count_; } // generalized copy constructor template <typename U> shared_ptr(const shared_ptr<U>& other) : ptr_(other.ptr_), count_(other.count_) { ++*count_; } shared_ptr& operator=(const shared_ptr& other) { shared_ptr(other).swap(*this); return *this; } shared_ptr(shared_ptr&& other) noexcept : ptr_(other.ptr_), count_(other.count_) { other.ptr_ = nullptr; other.count_ = new std::atomic<std::size_t>(1); } shared_ptr& operator=(shared_ptr&& other) noexcept { shared_ptr(std::move(other)).swap(*this); return *this; } void swap(shared_ptr& other) noexcept { using std::swap; swap(ptr_, other.ptr_); swap(count_, other.count_); } void reset(T* ptr) noexcept { shared_ptr(ptr).swap(*this); } T& operator*() const { return *ptr_; } T* operator->() const { return ptr_; } std::size_t use_count() const { return ptr_ == nullptr? 0: count_->load(); } explicit operator bool() const { return ptr_ != nullptr; } private: T* ptr_; std::atomic<std::size_t>* count_; };
Вот некоторые основные моменты:
- Счетчик ссылок увеличивается, когда новый экземпляр
shared_ptr
создается из существующего путем создания копии или назначения копии (правая сторона). Счетчик ссылок уменьшается, когда экземплярshared_ptr
выходит за пределы области действия, назначается копирование (слева) или когда вызывается функцияreset
. Счетчик ссылок используется для отслеживания того, сколько экземпляровshared_ptr
совместно владеют одним и тем же динамически выделяемым объектом. Когда счетчик ссылок достигает нуля, это означает, что больше нет экземпляровshared_ptr
, которые совместно владеют объектом, поэтому его можно безопасно удалить. - Метод копирования и замены используется для реализации оператора копирования-присваивания. Идея этого метода заключается в определении временного экземпляра
shared_ptr
и обмене его содержимым с целевым экземпляромshared_ptr
. Этот подход гарантирует, что целевой экземплярshared_ptr
останется в допустимом состоянии, даже если присваивание завершится ошибкой из-за возникновения исключения. Временный экземплярshared_ptr
уничтожается в конце области действия, а счетчик ссылок динамически выделяемого объекта корректно управляется. Точно так же методreset
использует ту же технику. И оператор присваивания перемещения использует технику перемещения и замены. - Использование атомарного значения для счетчика ссылок должно сделать его потокобезопасным. В многопоточной среде несколько потоков могут одновременно обращаться к счетчику ссылок и изменять его, а атомарный тип обеспечивает необходимую синхронизацию, гарантирующую согласованное обновление счетчика ссылок между потоками.
- Обобщенный конструктор копирования позволяет создавать
shared_ptr
экземпляров изshared_ptr
экземпляров разных, но связанных типов. Это полезно при работе с отношениями наследования между типами. Например, если классDerived
является производным от классаBase
, можно создать экземплярshared_ptr
, указывающий на объект типаDerived
, а затем использовать обобщенный конструктор копирования для создания нового экземпляраshared_ptr
, который разделяет владение с исходным.shared_ptr
, но имеет другой тип, связанный с исходным типом. Таким образом,shared_ptr
можно использовать для управления временем жизни объекта производного типа, сохраняя при этом возможность использовать связь между производным типом и базовым типом. Обратите внимание, что объявление класса друга в начале объявления класса необходимо, чтобы заставить его работать. В противном случаеother.ptr_
недоступен, посколькуshared_ptr<U>
— это другой класс, аptr_
— закрытый.
Вот несколько тестов для проверки функциональности shared_ptr
:
struct Foo { Foo() { std::cout << "Foo created\n"; } ~Foo() { std::cout << "Foo destroyed\n"; } }; struct Base { virtual void bar() { std::cout << "Base\n"; } }; struct Derived: public Base { void bar() override { std::cout << "Derived\n"; } }; int main() { // Test 1: Construct shared_ptr and verify the object is created { shared_ptr<Foo> p1(new Foo); // Foo created std::cout << "Use count: " << p1.use_count() << '\n'; // outputs 1 } // Foo destroyed // Test 2: Copy construct shared_ptr and verify use count is updated { shared_ptr<Foo> p1(new Foo); // Foo created shared_ptr<Foo> p2(p1); std::cout << "Use count: " << p1.use_count() << '\n'; // outputs 2 } // Foo destroyed // Test 3: Copy assignment shared_ptr and verify use count is updated { shared_ptr<Foo> p1(new Foo); // Foo created shared_ptr<Foo> p2(new Foo); // Foo created p1 = p2; // Foo destroyed std::cout << "Use count: " << p1.use_count() << '\n'; // outputs 2 std::cout << "Use count: " << p2.use_count() << '\n'; // outputs 2 } // Foo destroyed // Test 4: Reset shared_ptr and verify the original object is destroyed { shared_ptr<Foo> p1(new Foo); // Foo created p1.reset(nullptr); // Foo destroyed } // Test 5: Move shared_ptr and verify the moved-from instance has a nullptr { shared_ptr<Foo> p1(new Foo); // Foo created shared_ptr<Foo> p2 = std::move(p1); std::cout << "Use count: " << p2.use_count() << '\n'; // outputs 1 std::cout << "p1 is null: " << (p1 ? "false" : "true") << '\n'; // outputs true } // Foo destroyed // Test 6: Move assignment and verify the moved-from instance has a nullptr { shared_ptr<Foo> p1(new Foo); // Foo created shared_ptr<Foo> p2; p2 = std::move(p1); std::cout << "Use count: " << p2.use_count() << '\n'; // outputs 1 std::cout << "p1 is null: " << (p1 ? "false" : "true") << '\n'; // outputs true } // Foo destroyed // Test 7: Generalized copy constructor with related types { shared_ptr<Foo> p1(new Foo); // Foo created shared_ptr<Foo> p2(p1); shared_ptr<const Foo> p3(p2); std::cout << "Use count: " << p3.use_count() << '\n'; // outputs 3 } // Foo destroyed // Test 8: Generalized copy constructor with related types { shared_ptr<Derived> p1(new Derived); shared_ptr<Base> p2(p1); p2->bar(); // outputs Derived } return 0; }
Эти тесты проверяют основные функции, такие как создание shared_ptr
, копирование shared_ptr
, сброс shared_ptr
, перемещение shared_ptr
и использование обобщенного конструктора копирования для создания shared_ptr
родственного типа.
Связанное чтение