#опытным
В прошлом посте вы могли заметить, что все специальные методы класса Serializer только объявляются в хэдере, а реализуются в файле исходников. Зачем так делать?
class Serializer {
public:
Serializer();
~Serializer();
// only move-semantic
Serializer(const Serializer&) = delete;
Serializer& operator=(const Serializer&) = delete;
Serializer(Serializer&&) noexcept;
Serializer& operator=(Serializer&&) noexcept;
//...
private:
std::unique_ptr<struct Impl> pimpl_;
};Мы ведь привыкли либо вообще не определять никакие специальные методы по правилу нуля, либо просто помечать их default и все. Почему в этот раз так нельзя сделать?
Все дело в нашем особенном указателе pimpl_. Это умный указатель на forward-объявленную структуру. Это значит, что мы не знаем примерно ничего об этой структуре, кроме ее имени. И в этом загвоздка.
Когда мы явно помечаем специальные методы класса как default, мы явно просим компилятор за нас сгенерировать определение этих методов.
class Serializer {
public:
Serializer();
~Serializer() = default; // Here
// only move-semantic
Serializer(const Serializer&) = delete;
Serializer& operator=(const Serializer&) = delete;
Serializer(Serializer&&) noexcept = default; // Here
Serializer& operator=(Serializer&&) noexcept = default; // Here
//...
private:
std::unique_ptr<struct Impl> pimpl_;
};То есть мы запрашиваем определение метода в теле класса. На основе чего компилятор будет генерировать их? На основе того, что есть в теле класса.
Но вот беда, в теле класса нет никакой информации о том, что из себя представляет класс Impl. Возьмем тот же деструтор. У std::unique_ptr тип делитера зашит в шаблонный параметр. По дефолту это default_deleter, который вызывает просто
delete ptr . А у delete expression есть пометка, что при удалении указателя на неполный тип программа становится ill-formed. Поэтому компиляторы на случай, если вы захотите сгенерировать деструктор std::unique_ptr с неполным типом, ставят такой ассерт времени компиляции: static_assert(sizeof(_Tp)>0, "can't delete pointer to incomplete type");. А компиляция последнего примера крашнется с соотвествующей ошибкой.С перемещающими операциями похожая история. Когда мы используем операции std::unique_ptr мы инстанцируем этот шаблон с конкретными шаблонными параметрами. Значит должен инстанцироваться и default_deleter, который не может работать с неполными типами, как уже было сказано выше.
Так что определяйте специальные методы в реализации при использовании pimpl и будет вам счастье.
Don't give impossible tasks. Stay cool.
#cppcore #design