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

272
25 ноября 2017, 10:36

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

// in header file
class widget 
{
public:
    widget();
    ~widget();
private:
    class impl;
    unique_ptr<impl> 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->();
    }
Answer 1

Для начала нужно понимать разницу в идеологии между shared_ptr и unique_ptr. Первый подразумевает, что объект будет в общем владении, а unique_ptr только единоличное владение. Так как pimpl подразумевает единоличное владение (не, можно конечно сделать и расшаренное, но это как то не нормально), то unique_ptr в самый раз.

Почему же оно быстрее? все очень просто. shared_ptr внутри себя содержит как минимум счетчик ссылок либо на атомиках, либо с мютексами. В любом случае это не быстро. unique_ptr это все не нужно. В многих случаях компилятор может оптимизировать до такой степени, что будет не хуже голого указателя.

почему предлагается не разделять реализацию между несколькими экземплярами

потому что это будет уже не pimpl.

дополнить ее вот такими реализациями операторов, характерными для COW идиомы:

Ваша реализация требует наличие некой функции copy. И второе - она не очень thread-safe.

Почему такое не сделали в shared_ptr? Потому что это был бы cow_ptr. Это лишняя функциональность, которая обычно не нужна (если бы она была нужна, то наверно она появилась бы и в бусте, откуда пришел этот умный указатель), а в с++ не принято "платить за то, что не используется".

Answer 2

Если вы дополните умный указатель этими операторами - это будет уже не shared_ptr, а уже какой-нибудь cow_ptr. И да, так тоже будет работать. Тут главное - найти способ вынести copy() в модуль реализации, потому что нельзя скопировать необъявленный тип данных.

Как-то так в итоге должно получиться:

class widget 
{
public:
    widget();
    ~widget();
private:
    class impl;
    impl* copy(pimpl* impl);
    cow_ptr<impl, &widget::copy> pimpl;
};

Однако, на самом деле операция копирования объекта - очень странная по смыслу. И в большинстве прикладных случаев ее явно запрещают. А если копирование запрещено архитектурно, то зачем все эти сложности с реализацией?

READ ALSO
Преобразовать char* в массив double [C++]

Преобразовать char* в массив double [C++]

Здравствуйте, задача состоит в следующем : Изначально нам дан массив double, который нужно зашифровать xor-ом, я привожу массив double к char массиву...

267
QWebEngine pdf рендер таблиц

QWebEngine pdf рендер таблиц

Для формирования отчетов использую QWebEnginePage, из которого можно производить печать в PDF, все очень удобно, но присутствует проблема с печатью...

220
Opengl 3.x ошибка gl.h included before glew.h

Opengl 3.x ошибка gl.h included before glew.h

Изучаю Opengl 3x в исходном коде урока произошла ошибка gl

254
Чтение из файла в структуру С++

Чтение из файла в структуру С++

задача - составить программу для управления даннымиДанные считываются из файла в структуру, из структуры в вектор(для редактирования и так...

225