При використанні C++ іноді потрібно отримати shared_ptr
до екземпляра класу зсередини самого класу. Тут і вступає в гру std::enable_shared_from_this
. Однак є три підводні камені, на які слід звернути увагу при використанні std::enable_shared_from_this
:
-
Не використовуйте
shared_from_this()
в конструкторі: Використанняshared_from_this()
в конструкторі призведе до викиду виключенняstd::bad_weak_ptr
. Дивіться Приклад 1 нижче. -
Об'єкт повинен управлятися через
shared_ptr
:shared_from_this()
працюватиме лише якщо об'єкт управляється черезshared_ptr
; в іншому випадку буде викинуто виключенняstd::bad_weak_ptr
. Дивіться Приклад 2 нижче. -
Клас повинен публічно наслідувати
std::enable_shared_from_this
: Клас повинен публічно наслідуватиstd::enable_shared_from_this
; спадкування не може бути захищеним або приватним, в іншому випадку буде викинуто виключенняstd::bad_weak_ptr
.
Наведену ситуацію можна відтворити, використовуючи wandbox.
Чому існують ці обмеження? Ця стаття намагатиметься пояснити причини з точки зору вихідного коду std::enable_shared_from_this
. (Це пояснення базується на реалізації в clang libc++, вихідний код доступний за адресою: shared_ptr.h#L1433).
#include
// Ситуація 1: Використання shared_from_this в конструкторі
class Case1 : public std::enable_shared_from_this{
public:
Case1(){
// виключення: термінування через необроблене виключення типу std::__1::bad_weak_ptr: bad_weak_ptr
auto case1 = shared_from_this();
}
};
// Ситуація 2: Не управляється через shared_ptr
class Case2 : public std::enable_shared_from_this{
public:
std::shared_ptr get_shared_ptr() {
// виключення: термінування через необроблене виключення типу std::__1::bad_weak_ptr: bad_weak_ptr
return shared_from_this();
}
};
// Ситуація 3: Не публічне наслідування від std::enable_shared_from_this
class Case3 : std::enable_shared_from_this{
public:
std::shared_ptr get_shared_ptr() {
// виключення: термінування через необроблене виключення типу std::__1::bad_weak_ptr: bad_weak_ptr
return shared_from_this();
}
};
int main(){
// Ситуація 1:
auto c1 = std::make_shared();
// Ситуація 2:
Case2* c2 = new Case2();
c2->get_shared_ptr();
// Ситуація 3:
auto c3 = std::make_shared();
c3->get_shared_ptr();
return 0;
}
Ось уривок з вихідного коду enable_shared_from_this
, з деякою менш важливою логікою, яка була видалена для полегшення розуміння. Код виглядає наступним чином:
```c++
template
class enable_shared_from_this {
mutable weak_ptr<_Tp> __weak_this_;
public:
shared_ptr<_Tp> shared_from_this() {
return shared_ptr<_Tp>(__weak_this_);
}
template
friend class shared_ptr;
};
З коду видно, що основою `enable_shared_from_this` є атрибут `weak_ptr`, який називається `__weak_this_`.
Функція `shared_from_this` фактично перетворює цей `weak_ptr` на `shared_ptr`.
Тож коли ж ініціалізується `__weak_this_`? Відповідь: коли створюється об'єкт `shared_ptr`.
Нижче наведена логіка створення об'єкта в `shared_ptr`, де атрибут `__weak_this_` класу `enable_shared_from_this` ініціалізується в функції `__enable_weak_this`.
template
static sharedptr<Tp> createwithcontrolblock(Yp* _p, _CntrlBlk* _cntrl) NOEXCEPT {
sharedptr<Tp> _r;
__r.ptr_ = p;
__r.cntrl_ = cntrl;
// set _weakthis_
__r.enableweakthis(r.ptr, _r._ptr);
return __r;
}
```
В реалізації __enable_weak_this
, оскільки клас enable_shared_from_this
оголосив shared_ptr
як другий клас, shared_ptr
може безпосередньо доступати та встановлювати атрибут __weak_this_
класу enable_shared_from_this
.
Крім того, __enable_weak_this
використовує SFINAE (Substitution Failure Is Not An Error) для реалізації шаблонного узгодження. Це означає, що __weak_this_
буде встановлено лише у випадку, якщо виконується умова __enable_if_t*>::value, int> = 0
(тобто клас може бути перетворений на enable_shared_from_this
, що означає, що клас публічно успадковує від enable_shared_from_this
). В іншому випадку буде вибрано порожню реалізацію.
template *>::value, int> = 0>
void __enable_weak_this(const enable_shared_from_this<_Yp>* __e, _OrigPtr* __ptr) _NOEXCEPT {
typedef __remove_cv_t<_Yp> _RawYp;
if (__e && __e->__weak_this_.expired()) {
__e->__weak_this_ = shared_ptr<_RawYp>(*this, const_cast<_RawYp*>(static_cast(__ptr)));
}
}
void __enable_weak_this(...) _NOEXCEPT {}
Після інтерпретації вихідного коду все стає зрозумілим. Тепер давайте ще раз розглянемо три підводні камені, згадані на початку статті:
- Ситуація 1: Ви не можете використовувати
shared_from_this()
в конструкторі. Це тому, що весь процес полягає спочатку в створенні оригінального об'єкта, потім в налаштуванні атрибута__weak_this_
і, в кінці, в отриманні об'єктаshared_ptr
. Тому під час виконання конструктора оригінального об'єкта атрибут__weak_this_
ще не був налаштований, томуshared_from_this
не можна використовувати. - Ситуація 2: Створений об'єкт повинен управлятися через
shared_ptr
, щобshared_from_this()
працював. Це тому, що атрибут__weak_this_
налаштовується лише в межахshared_ptr
. - Ситуація 3: Відповідний клас повинен публічно успадковувати від
std::enable_shared_from_this
. Це тому, що тільки при публічному наслідуванні відповідний__enable_weak_this
може бути правильно підібраний, що дозволяє налаштувати атрибут__weak_this_
.
Перекладено з: interpreting std::enablesharedfrom_this from libc++ source code