Как стать автором
Обновить

Комментарии 28

Если вы заметили на своем костюме пятно, не огорчайтесь. Это поправимо. Hапример, пятна от растительного масла легко выводятся бензином. Пятна от бензина легко снимаются раствором щелочи. Пятна от щелочи исчезают от уксусной эссенции. Следы от уксусной эссенции надо потереть подсолнечным маслом. Hу, а как выводить пятна от подсолнечного масла, вы уже знаете.
Именно этот боян мне и вспомнился, когда я дочитал про все эти воркэраунды.
Дык за это мы С++ и любим!
Вы может и любите, а мы не очень. Нам Си интереснее. :)
Чак Паланик всегда предлагал хорошие рецепты. Это ведь его цитата?
НЛО прилетело и опубликовало эту надпись здесь
Что-нибудь поменяется в pimpl с приходом C++11?
В минусы еще стоит записать тяжелую читабельность кода, написанного с идиомой pimpl.
Пара макросов и жизнь прекрасна)
… и проблемы с отладкой :)
Какие?
Что-то не припомню IDE, позволяющую отлаживать сгенерённый макросами код
Чем больше я читаю таких статей, тем более мне нравится программировать на С.
Я недавно открыл для себя идиому Pimpl как замену интерфейсам,
построенным на чисто виртуальных функциях.
К примеру есть интерфейс IFoo с набором чисто виртуальных функций:

class IFoo
{
public:
virtual void f1() = 0;
virtual void f2() = 0;
virtual void f3() = 0;
};

Этот интерфейс можно переписать на Pimpl и убрать виртуальные функции:

//foo.h
class CFoo;
class IFoo
{
public:
IFoo(CFoo* foo);
void f1();
void f2();
void f3();
private:
CFoo* m_foo;
};

А вот как будет выглядеть реализация этого интерфейса:

//foo.cpp
class CFoo: public IFoo
{
public:
CFoo(): IFoo(this) {}
void f1(){}
void f2(){}
void f3(){}
};
IFoo::IFoo(CFoo* foo): m_foo(foo) {}
void IFoo::f1() {m_foo->f1();}
void IFoo::f2() {m_foo->f2();}
void IFoo::f3() {m_foo->f3();}

Подобную замену я производил в процессе рефакторинга, в уже написанном коде.
При таком подходе никакой потери производительности нет,
даже наоборот — удалось избавится от вызовов виртуальных функций.
Недостатком является отсуствие динамического полиморфизма.
Но в моем случае он как-раз таки был и не нужен.
Вы изобрели DI, что не есть pimpl.
Ну основноое свойство Pimpl (Pointer to private implementation) в моем подходе сохранено.
Где сказано что Pimpl обязан вести себя как класс-значение и содержать внутри умный указатель?
Если я правильно понял Вашу идею, то вы передаете impl извне. Класс-обертка не знает о реализацие. Таким образом, у Вас ни в коем случае не private implementation. Хотя, возможно, я неправильно понял Ваши цель и задумку.
Private класс в моем примере является наследником (единственным) класса-обертки, и создается фабричным методом.
Не ясно в чем профит от такого подхода.
Основная фича pimpl, с моей точки зрения, это уменьшения времени компиляции за счет того, что тяжелая имплементация инклудится только в одном cpp файле. Дополнительной фичей получаем корректный генерированный конструктор копирования. В Вашем случая, на сколько я понимаю, имплементация инклудится, как минимум, в двух cpp файлах.
Заголовочный файл в моем примере содержит лишь предварительную декларацию Private класса и не содержит реализацию класса-обертки.
Вот содержимое файла foo.h:

class CFoo; // предварительная декларация приватного класса
class IFoo
{
public:
&nbsp&nbsp&nbsp&nbspIFoo(CFoo* foo);
&nbsp&nbsp&nbsp&nbspvoid f1();
&nbsp&nbsp&nbsp&nbspvoid f2();
&nbsp&nbsp&nbsp&nbspvoid f3();
private:
&nbsp&nbsp&nbsp&nbspCFoo* m_foo;
};
cpp класса IFoo должен включать заголовок CFoo. cpp Вашей фабрики, которая порождает CFoo нуждается в заголовке CFoo. Это два места. При добавление новых фич могут появится еще места, в которых понадобится заголовок CFoo. Получается, что Вы вынесли реализацию наружу. Возможно спрятали ее за фабрику, но фабрика не предоставляет полиморфизма. Зачем нужен такой pimpl и зачем нужна такая фабрика? В чем профит?
Профит в избавлении от паразитных виртуальных функций.
Это мне очень помогло при рефакторинге, когда виртуальные функции интерфейса переопределяли кому не лень во всех классах-наследниках.

А насчет множественных включений файлов, то не вижу проблемы в том, чтобы разместить фабричный метод в том-же foo.cpp файле.
Я перед собой не ставил такую цель. В моем случае фабричным методом является отдельная библиотека, которая наружу выдает лишь интерфейс объекта, а внутри «видит» реализацию этого интерфейса.
Мы не совсем друг-друга понимаем. Я знаю в чем преимущества классического pimpl. Я не понимаю, в чем преимущества Вашей реализации pimpl.
Приведу пример — человек видит классический pimpl — он знает, что от него ожидать.
Человек видит DI и фабрику — ожидает возможность настройки поведения обертки подменой имплементации.
Зачем было добавлять фабрику и параметризацию обертки?
Фабрику я не добавлял, я производил рефакторинг существующего кода, с готовой фабрикой, которая возвращала интерфейс, построенный на чисто-виртуальных функциях (классический подход).

Одной из моих целей была обратная совместимость интерфейсов без необходимости переписывать код, использующий этот интерфейс.
По этой причине pimpl в классическом исполнении внедрять было нецелесообразно.

А «параметризация обертки» это, так скажем, гарантия того, что к интерфейсу не будет приобразован какой-нибудь другой класс.
Впринципи в методах обертки можно было бы использовать static_cast(this), т.к. у нее в программе должен быть один единственный класс-наследник.
В моем примере только один cpp файл, который содержит реализацию приватного класса и реализацию функций класса-обертки. Как по мне типичный pimpl.
> удалось избавится от вызовов виртуальных функций.

Зато приобрели индиректный вызов методов по указателю и усложнение реализации и сопровождения.
Оптимизатор намного лучше справляется с такими методами,
к примеру может заинлайнить, что невозможно с виртуальными функциями.

А вот насчет усложнения сопровождения совершенно не согласен.
Виртуальные функции сопровождать сложнее,
так как их можно переопределить в любом классе-наследнике (что и было в моем случае).

Тоесть под интерфейсом у меня скрываается иерархия классов,
и любая из интерфейсных функций могла быть переопределена в любом классе-наследнике.
И это невозможно было контролировать.
Убрав виртуальные функции из интерфейса я отделил несколько функций,
которые действительно должны были быть виртуальными от функций интерфейса,
и применил к ним подход NVI.
Достаточно часто использовал идиомы pimpl, ни с одной из проблем ни реальных, ни в тестовых/эксперементальных проектах не сталкивался.
Pimpl и const — достаточно правильно строить класс и определять методы pimpl как const, никаких оберток не нужно. Вот, например, метод определенный в классе pimpl.
void TestImpl::impl::constMethod(int value) const { _value = value; }

И теперь при вызове:
void TestPimpl::constMethod(int value) const
{
_pimpl->constMethod(value);
}

Мы получаем ошибку компилятора о const-некорректности.

2-ая проблема не касается pimpl конкретно. Тоже самое будет если и без pimpl инициализировать какую-либо переменную в конструторе, передав ей указатель на сам класс.
someClass::someClass(): _someVar(this)

Естественно если в конструкторе _someVar вызывать методы someClass это приведет к undefined behaviour, это, кстати, обьясняют на первых курсах в ВУЗе.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории