Russian Qt Forum
Ноябрь 23, 2024, 07:56 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Pimpl inheritance через member shadowing  (Прочитано 4996 раз)
kramer
Гость
« : Март 06, 2013, 18:14 »

Друзья! Я тут клепал PIMPL на макросах а-ля Qt, и мне подумалось, а что собственно плохого в member shadowing? Пожалуйста, скажите, будет ли это работать, и если нет, то почему? Компилятора под рукой нет, пишу с коробка спичек, а до завтра ждать не хочется:
Код
C++ (Qt)
class BaseImpl;
class DerivedImpl;
class Base {
public:
  Base() : impl(new BaseImpl) {}
  virtual ~Base() { delete Impl; }
protected:
  Base(BaseImpl* i) : impl(i) {}
private:
  BaseImpl* impl;
};
 
class BaseImpl {
public:
  BaseImpl() {}
  virtual ~BaseImpl();
};
 
class Derived : public Base {
public:
  Derived() : Base(impl = new DerivedImpl) {}
  virtual ~Derived() {}
protected:
  Derived(DerivedImpl* i) : BaseImpl(i), impl(i) {}
private:
  DerivedImpl* impl;
};
 
class DerivedImpl : public BaseImpl {
public:
  DerivedImpl() {}
  virtual ~DerivedImpl() {}
};
 

Т.е. мой ход мыслей таков: при создании класса-наследника через Derived() базовый класс получит срезку BaseImpl, и указатели Base::impl и Derived::impl будут одинаковыми, просто разных типов. Виртуальные функции, понятно, будут использовать соответствующий классу указатель, и наконец, во благовременье будет вызван виртуальный деструктор класса реализации, который и уничтожит созданное. А? Или я что-то недодумал?
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #1 : Март 06, 2013, 18:59 »

Не компилил, полагаю что такая конструкция допустима, но выигрыша от нее никакого. Когда будете писать методы для Deived, он достанет "какoй impl - тот или этот". Придется писать Derived::impl - ну и за что боролись? 
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #2 : Март 06, 2013, 19:06 »

Работать будет, но немного не так. В классе Derived будет ДВА указателя (DerivedImpl *impl и BaseImpl *Base::impl). Работать будет как для двух отдельных указателей на отдельные объекты.
Такой подход применяется в KDE - они на каждом уровне иерархии открывают *d заново.
Подход кутешников гораздо лучше. В вашем подходе (если кол-во классов в иерархии N) - N+1 выделений памяти (вместо 2), размер результирующего класса N*sizeof(void*) (вместо 1*sizeof(void*)), да и памяти больше за счет новых втейблов в Private классах.
Всё мелочи, кроме большего количества вызовов malloc - это операция относительно дорогая и лучше ее не дергать лишний раз.
Да, а еще d_func'и сохраняют константность d, в отличие от голого указателя в классе (то есть в конст ф-ии он просто так не даст поменять мембер)

Igors
Компилер возьмет верхний в иерархии impl (к-ый "ближе" по областям видимости)
« Последнее редактирование: Март 06, 2013, 19:08 от Авварон » Записан
kramer
Гость
« Ответ #3 : Март 06, 2013, 19:22 »

В классе Derived будет ДВА указателя (DerivedImpl *impl и BaseImpl *Base::impl). Работать будет как для двух отдельных указателей на отдельные объекты.

Т.е. таким образом, вы хотите сказать, что в коде
Код
C++ (Qt)
class Base {};
class Derived : public Base {};
Derived *d = new Derived;
Base *b = d;
 
указатели b и d будут иметь разные значения?

UPD: Засада в том, что кутешный подход завязан на темплейтах, а у меня GCC 3.4 и шланг под маком. Я на GCC 3.4 с темплейтами один раз так обжегся, с тех пор на воду дую. Улыбающийся
« Последнее редактирование: Март 06, 2013, 19:40 от kramer » Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #4 : Март 06, 2013, 19:36 »

Нет, с чего бы?
У вас случай
Код:
class Derived
{
private:
    BaseImpl *impl1;
    DerivedImpl *impl2;
};
Записан
kramer
Гость
« Ответ #5 : Март 06, 2013, 19:46 »

Нет, с чего бы?
У вас случай
Код:
class Derived
{
private:
    BaseImpl *impl1;
    DerivedImpl *impl2;
};
Ну и господь с ним, лишних 8 байт на указатель не жалко, при моих-то 64Гб минимум. Улыбающийся Просто из вашего ответа можно заключить, что для каждого члена иерархии создается отдельный класс реализации - однако в моем случае это не так, создается только класс реализации для вновь создаваемого класса, а всем предкам передается на него указатель, который благополучно срезается, и, кстати, вызов new тут получается только один.

P.S. Я отредактировал предыдущий ответ, объяснив, почему мне не нравится d_func().
« Последнее редактирование: Март 06, 2013, 19:56 от kramer » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #6 : Март 06, 2013, 19:55 »

вызов new тут получается только один.
Согласен, 2 указателя равны. Все же как-то это "нездорово". Напр как будет выглядеть установка другого PIMPL?
Записан
kramer
Гость
« Ответ #7 : Март 06, 2013, 20:08 »

Согласен, 2 указателя равны. Все же как-то это "нездорово". Напр как будет выглядеть установка другого PIMPL?
Да я тоже думаю, что "нездорово", потому и спрашиваю. Мой PIMPL не подразумевает разных реализаций (если я правильно вас понял), он преследует только одну цель - спрятать реализацию от пользователя SDK, и заодно улучшить бинарную совместимость. А то там внутри такой бардак, стыдно глянуть. Если пользователю придет в голову использовать мой PIMPL - никто не мешает ему отнаследоваться от моих приватных классов, которые, пусть и нехотя, но все-таки поставляются с SDK. А если ему нужен свой собственный PIMPL - ну так пускай использует для него другое имя.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #8 : Март 06, 2013, 20:45 »

А, я не заметил, что у вас передаётся impl потомка предку; каюсь. Да, так экземпляр приватного класса будет 1.
На самом деле не вижу, чем вам не нравится d_func() кутешный. Шаблоны там банальные, не могу представить где это не будет работать (ведь мембер-темплейты тут не нужны).
Если боитесь шаблонов, напишите свой DECLARE_PRIVATE на макросах:
Код:
#define DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } \
    inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } \
    friend class Class##Private;
Записан
kramer
Гость
« Ответ #9 : Март 14, 2013, 14:44 »

Друзья! Я тут нашел любопытное решение для конст-корректности, которое плюс ко всему позволяет убрать в реализацию сам факт использования PIMPL, и теперь спешу поделиться. Нужно создать обертку для void* в виде
Код
C++ (Qt)
class _qualified_ptr {
public:
_qualified_ptr() : _ptr(NULL) {}
_qualified_ptr(void* ptr) : _ptr(ptr) {}
inline operator void*() { return _ptr; }
inline operator const void*() const { return _ptr; }
private:
void* _ptr;
};
 
и в базовых классах интерфейса и реализации хранить ссылку на реализации и интерфейс в виде члена этого типа вместо BaseImpl* или void*. Инициализация проводится как и раньше, через защищенный конструктор, а для разыменования используются четыре макроса:
Код
C++ (Qt)
#define D_CAST(Class) \
Class##Private* d = static_cast<Class##Private*>((void*)__impptr)
#define F_CAST(Class) \
Class* f = static_cast<Class*>((void*)__ifptr)
#define D_CAST_CONST(Class) \
const Class##Private* d = static_cast<const Class##Private*>((const void*)__impptr)
#define F_CAST_CONST() \
const Class* f = static_cast<const Class*>((const void*)__ifptr)
 
Таким образом в заголовочном файле класса-потомка классе не будет вообще никаких упоминаний о PIMPL или классе реализации, и можно при необходимости его добавить, не внося никаких изменений в интерфейс. Вот мой код целиком:
Код
C++ (Qt)
 
#include <iostream>
 
#define D_CAST(Class) \
Class##Private* d = static_cast<Class##Private*>((void*)__impptr)
 
#define F_CAST(Class) \
Class* f = static_cast<Class*>((void*)__ifptr)
 
#define D_CAST_CONST(Class) \
const Class##Private* d = static_cast<const Class##Private*>((const void*)__impptr)
 
#define F_CAST_CONST() \
const Class* f = static_cast<const Class*>((const void*)__ifptr)
 
 
class _qualified_ptr {
public:
_qualified_ptr() : _ptr(NULL) {}
_qualified_ptr(void* ptr) : _ptr(ptr) {}
 
inline operator void*() { return _ptr; }
inline operator const void*() const { return _ptr; }
 
private:
void* _ptr;
};
 
class Base {
public:
Base();
virtual ~Base();
 
void foo();
virtual void boom();
 
protected:
Base(void*);
_qualified_ptr __impptr;
};
 
class BasePrivate {
public:
BasePrivate(Base* base);
virtual ~BasePrivate();
int x;
 
void bar();
 
protected:
void* __ifptr;
};
 
BasePrivate::BasePrivate(Base* base) : __ifptr(base) { std::cout << "BasePrivate::BasePrivate(Base*)" << std::endl; }
BasePrivate::~BasePrivate() { std::cout << "BasePrivate::~BasePrivate()" << std::endl; }
 
void BasePrivate::bar()
{
F_CAST(Base);
std::cout << "BasePrivate::bar()" << std::endl;
(void)f;
}
 
Base::Base() : __impptr(new BasePrivate(this)) { std::cout << "Base::Base()" << std::endl; };
Base::Base(void* ptr) : __impptr(ptr) { std::cout << "Base::Base(void*)" << std::endl; }
Base::~Base() { D_CAST(Base); delete d; std::cout << "Base::~Base()" << std::endl; }
 
void Base::foo()
{
D_CAST(Base);
std::cout << "Base::foo()" << std::endl;
d->bar();
}
 
void Base::boom() {
D_CAST(Base);
std::cout << "Base::boom(), BasePrivate::x = " << d->x << std::endl;
}
 
class Derived : public Base {
public:
Derived();
virtual ~Derived();
 
void baz();
void baz() const;
virtual void boom();
protected:
Derived(void*);
};
 
class DerivedPrivate : public BasePrivate {
public:
DerivedPrivate(Derived* derived);
~DerivedPrivate();
};
 
DerivedPrivate::DerivedPrivate(Derived* derived) : BasePrivate(derived) { std::cout << "DerivedPrivate::DerivedPrivate(Derived*)" << std::endl; }
DerivedPrivate::~DerivedPrivate() { std::cout << "DerivedPrivate::~DerivedPrivate()" << std::endl; }
 
Derived::Derived() : Base(new DerivedPrivate(this)) { std::cout << "Derived::Derived()" << std::endl; }
Derived::~Derived() { std::cout << "Derived::~Derived()" << std::endl; }
 
void Derived::baz() {
D_CAST(Derived);
std::cout << "Derived::baz()" << std::endl;
d->bar();
}
 
void Derived::baz() const {
D_CAST_CONST(Derived);
std::cout << "Derived::baz() const" << std::endl;
(void)d;
//d->bar(); //error: passing ‘const DerivedPrivate’ as ‘this’ argument of ‘void BasePrivate::bar()’ discards qualifiers [-fpermissive]
}
 
void Derived::boom()
{
D_CAST(Derived);
std::cout << "Derived::boom(), BasePrivate::x = " << d->x << std::endl;
}
 
int main(int argc, char** argv)
{
Base* b = new Base();
b->foo();
delete b;
std::cout << "\n ===== \n" << std::endl;
 
b = new Derived();
b->foo();
b->boom();
const Derived* c = dynamic_cast<const Derived*>(b);
 
//c->foo(); // error: passing ‘const Derived’ as ‘this’ argument of ‘void Base::foo()’ discards qualifiers [-fpermissive]
 
delete c;
 
return 0;
}
 

Вот так вот. Буду благодарен за комментарии.
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.267 секунд. Запросов: 22.