Інтерпретація std::enable_shared_from_this з вихідного коду libc++

pic

При використанні C++ іноді потрібно отримати shared_ptr до екземпляра класу зсередини самого класу. Тут і вступає в гру std::enable_shared_from_this. Однак є три підводні камені, на які слід звернути увагу при використанні std::enable_shared_from_this:

  1. Не використовуйте shared_from_this() в конструкторі: Використання shared_from_this() в конструкторі призведе до викиду виключення std::bad_weak_ptr. Дивіться Приклад 1 нижче.

  2. Об'єкт повинен управлятися через shared_ptr: shared_from_this() працюватиме лише якщо об'єкт управляється через shared_ptr; в іншому випадку буде викинуто виключення std::bad_weak_ptr. Дивіться Приклад 2 нижче.

  3. Клас повинен публічно наслідувати 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 {
shared
ptr<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

Leave a Reply

Your email address will not be published. Required fields are marked *