1

Тема: Вітруальний деструктор і ідіома pimpl

Вітаю, пробую гратись з ідіомою pimpl, написав для початку такий код:

// Test.hpp
#include <memory>
class Implementation;

class Test {
public:
    Test()          = default;
    virtual ~Test() = default;
private:
    std::unique_ptr<Implementation> m_pImpl;
};
// main.cpp
#include "Test.hpp"
int main() {

    return 0;
}

І вирішив попробувати його збілдити, очікуючи, що він впаде. Але компілятор чомусь зібрав такий код, але чому? Хіба він не мав сваритися, що клас Implementation не реалізований?

Подякували: wander1

2 Востаннє редагувалося mamkin haker (22.09.2021 20:51:38)

Re: Вітруальний деструктор і ідіома pimpl

Звісно зберется, main ж пустий
оголосіть в main клас Test test; і побачите що не зберется :D

3

Re: Вітруальний деструктор і ідіома pimpl

mimik написав:

Хіба він не мав сваритися, що клас Implementation не реалізований?

Мав, але є така штука, як: "ill-formed, no diagnostic required". І на те, чому так було зроблено є причини.

mamkin haker написав:

Звісно зберется, main ж пустий
оголосіть в main клас Test test; і побачите що не зберется :D

Питання було не в цьому *FACEPALM*

Подякували: Arete, ch0r_t, mimik3

4

Re: Вітруальний деструктор і ідіома pimpl

wander написав:
mimik написав:

Хіба він не мав сваритися, що клас Implementation не реалізований?

Мав, але є така штука, як: "ill-formed, no diagnostic required". І на те, чому так було зроблено є причини.

mamkin haker написав:

Звісно зберется, main ж пустий
оголосіть в main клас Test test; і побачите що не зберется :D

Питання було не в цьому *FACEPALM*

А, що за причини? Можна детальніше або де про це можна почитати?

5 Востаннє редагувалося ch0r_t (21.09.2021 19:47:10)

Re: Вітруальний деструктор і ідіома pimpl

NDR —

"No diagnostic required" indicates that some phraseology is ill-formed according to the language rules, but a compiler need not issue any diagnostic or error message. Usually, the reason is that trying to detect these situations would result in prohibitively long compile times.

https://en.cppreference.com/w/cpp/language/ndr


mimik написав:

А, що за причини? Можна детальніше або де про це можна почитати?

https://stackoverflow.com/questions/419 … row-in-c14
https://timsong-cpp.github.io/cppwp/intro.compliance

"— If a program contains a violation of a rule for which no diagnostic is required, this International Standard places no requirement on implementations with respect to that program."

6

Re: Вітруальний деструктор і ідіома pimpl

mimik написав:

А, що за причини? Можна детальніше або де про це можна почитати?

Ну, почнімо з того, що delete для вказівника на неповний тип - UB.
Деструктор за замовчуванням ~Test() генерує виклик деструктора ~std::unique_ptr<Implementation>, який містить delete для неповного типу Implementation.

Тепер, чому з = default це працює:

http://eel.is/c++draft/class.dtor#10 написав:

A destructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used or when it is explicitly defaulted after its first declaration.

Тобто, якщо у вас дефолтний деструктор, то він буде визначений лише при використанні ODR, тобто при знищенні об'єкта Test. У вашому фрагменті коду жоден об’єкт такого типу ніколи не знищується, і, отже, програма компілюється - оскільки той хто видаляє, а це unique_ptr насправді ніде не викликається. Він може бути викликаний лише деструктором Test, який не визначений у вашому випадку.

Коли ви зробите деструктор, як user-defined (тобто додасте йому тіло {}), то він визначиться на місці, і компілятор видасть своє "фе", оскільки ви не можете знищити об’єкт unique_ptr неповного типу.

До речі, оголошення деструктора, але не визначення (~Test();), не дасть помилки компіляції з тієї ж причини.

-------------------------------------------
Але це лише наслідок, а не причина.
Мені вже ліньки ритись в макулатурі стандарті, але як ви знаєте, формально "точкою визначення" такого деструктора буде самий кінець одиниці трансляції. Тобто для того, щоб код скомпілювати, не обов'язково надавати повне визначення Implementation саме вище за кодом. Достатньо надати його де завгодно в тих одиницях трансляції, що включають визначення Test. І, якщо підправити ваш код, згідно слів вище, то все запрацює:

class Implementation;

class Test {
public:
    Test()          = default;
    virtual ~Test() = default;
private:
    std::unique_ptr<Implementation> m_pImpl;
};

int main() {
    Test t;
}

class Implementation {};

І саме тому, такий код працює валідно теж:

class Implementation;

template <typename T> void foo();

int main() {
    foo<Implementation>();
}

class Implementation {};

template <typename T> void foo() {
    T x;
}

// Ось це компілятор добавить в кінець файлу неявно.
// template<>
// void foo<Implementation>() {
//     Implementation x = Implementation();
// }

І зроблено це спеціально для того, щоб дати компіляторам свободу просто ні про що не турбуючись інстанціювати всі шаблони в кінці файлу. Але при цьому потрібно, щоб в усіх точках інстанціювання, реальних і гіпотетичних, шаблон мав одну і ту ж інтерпретацію. В іншому випадку: ill-formed, no diagnostic required.

Подякували: ch0r_t, Arete, ReAl, mimik, leofun015