Страницы

Поиск по вопросам

среда, 4 марта 2020 г.

PIMPL на unique или shared указателях?

#cpp #перегрузка_операторов #smart_pointer


Привожу код заготовки реализации идиомы pImpl с этого сайта.

// in header file
class widget 
{
public:
    widget();
    ~widget();
private:
    class impl;
    unique_ptr pimpl;
};

// in implementation file
class widget::impl 
{
    // :::
};

widget::widget() : pimpl{ new impl{ /*...*/ } } { }
widget::~widget() { }


Далее там написано следующее:


  Prefer to hold the Pimpl using a unique_ptr. It’s more efficient than
  using a shared_ptr, and correctly expresses the intent that the Pimpl
  object should not be shared.


Вопрос: в чем конкретно эффективность (предпочтительное более быстрое перемещение?)
использования std::unique_ptr и почему предлагается не разделять реализацию между несколькими
экземплярами? Я ранее задавал вопрос относительно идиомы copy-on-write, почему бы не
использовать реализацию идиомы pImpl на указателе типа shared_ptr и дополнить ее вот
такими реализациями операторов, характерными для COW идиомы:

    // Non-const * and -> , copying
    T& operator*()
    {
        copy();
        return *m_sp;
    }

    T* operator->()
    {
        copy();
        return m_sp.operator->();
    }

    // Const * and -> methods no need to copy
    const T& operator*() const
    {
        return *m_sp;
    }

    const T* operator->() const
    {
        return m_sp.operator->();
    }

    


Ответы

Ответ 1



Для начала нужно понимать разницу в идеологии между shared_ptr и unique_ptr. Первый подразумевает, что объект будет в общем владении, а unique_ptr только единоличное владение. Так как pimpl подразумевает единоличное владение (не, можно конечно сделать и расшаренное, но это как то не нормально), то unique_ptr в самый раз. Почему же оно быстрее? все очень просто. shared_ptr внутри себя содержит как минимум счетчик ссылок либо на атомиках, либо с мютексами. В любом случае это не быстро. unique_ptr это все не нужно. В многих случаях компилятор может оптимизировать до такой степени, что будет не хуже голого указателя. почему предлагается не разделять реализацию между несколькими экземплярами потому что это будет уже не pimpl. дополнить ее вот такими реализациями операторов, характерными для COW идиомы: Ваша реализация требует наличие некой функции copy. И второе - она не очень thread-safe. Почему такое не сделали в shared_ptr? Потому что это был бы cow_ptr. Это лишняя функциональность, которая обычно не нужна (если бы она была нужна, то наверно она появилась бы и в бусте, откуда пришел этот умный указатель), а в с++ не принято "платить за то, что не используется".

Ответ 2



Если вы дополните умный указатель этими операторами - это будет уже не shared_ptr, а уже какой-нибудь cow_ptr. И да, так тоже будет работать. Тут главное - найти способ вынести copy() в модуль реализации, потому что нельзя скопировать необъявленный тип данных. Как-то так в итоге должно получиться: class widget { public: widget(); ~widget(); private: class impl; impl* copy(pimpl* impl); cow_ptr pimpl; }; Однако, на самом деле операция копирования объекта - очень странная по смыслу. И в большинстве прикладных случаев ее явно запрещают. А если копирование запрещено архитектурно, то зачем все эти сложности с реализацией?

Комментариев нет:

Отправить комментарий