Что-то не могу решить красиво, на первый взгляд, простую задачку:
class A
{
public:
A(...); // Какие-то параметрыprotected:
class Private;
typedef std::shared_ptr<Private> PrivatePtr;
A(PrivatePtr that);
PrivatePtr m_private; // Реализация
};
class B : public A
{
public:
B(...); // Какие-то параметрыprotected:
class Private;
typedef std::shared_ptr<Private> PrivatePtr;
PrivatePtr m_private; // Реализация
};
class A::Private
{
public:
Private(...); // Какие-то параметры
};
class B::Private : public A::Private
{
public:
Private(...); // Какие-то параметры
};
Структура классов как у паттерна Bridge, только у меня не интерфейсы и реализация, а открытая и закрытая реализация. Поэтому чем-то смахивает на Pimpl еще, но иерархии классов две.
Не могу придумать как в конструкторе производного класса B проинициализировать сразу и родителя (класс A) и переменную член. Может кто-нибудь подскажет идеи???
Если конструктор непрозрачный, то придётся повыкручиваться с инверсией.
Например, пусть A имеет статическую функцию, отображающую аргументы своего конструктора на кортеж аргументов конструктора реализации.
A::Private(AConfig) {...} // здесь - только логика создания реализации
A::A(aaa) : m_private(new A::Private(aconfig(aaa))) {} // а здесь - логика трансляции аргументов
B::Private(AConfig,BConfig) : A::Private(AConfig) {...} // здесь - только логика создания реализации, той и другой
B::B(aaabbb) : A(new B::Private(aconfig(aaa),bconfig(bbb))) {} // а здесь - и трансляция своих аргументов в аргументы предка, и своих в аргументы своей реализации
Здравствуйте, Кодт, Вы писали:
К>Если конструктор A прозрачный, т.е. вызывает конструктор A::Private с теми же аргументами, что переданы в A, то всё достаточно просто.
В вашей терминологии он прозрачный, да
К>
Вот тут и проблема, у меня, по задумке, указатели в B и A должны указывать на один и тотже объект (поэтому и shared_ptr), но на разные уровни в иерархии Private. А... даже если пока упростить и рассмотреть ваш вариант: я не понимаю как у вас теперь достучаться в B::Private из B класса? Или я туплю?
Здравствуйте, AcidTheProgrammer, Вы писали:
ATP>Вот тут и проблема, у меня, по задумке, указатели в B и A должны указывать на один и тотже объект (поэтому и shared_ptr), но на разные уровни в иерархии Private. А... даже если пока упростить и рассмотреть ваш вариант: я не понимаю как у вас теперь достучаться в B::Private из B класса? Или я туплю?
Во-первых, если нет желания делиться объектом-реализацией между несколькими разными экземплярами A или B, то shared_ptr оказывается избыточен.
Разве что спрятать деструктор реализации (shared_ptr запоминает его в рантайме).
Но это же можно сделать и с unique_ptr, у него есть параметр Deleter, реализация которого не обязана быть инлайном.
Во-вторых, это вопрос приведения типов, ну так и голые указатели/ссылки, и shared_ptr, и unique_ptr прекрасно приводятся и вверх, и вниз.
В сумме, получается такое
class A
{
class PrivateA;
struct PrivateDeleter
{
void operator()(PrivateA* p)const; // не инлайновый, определён после определения PrivateA, делает delete p;
};
typedef std::unique_ptr<PrivateA,PrivateDeleter> PrivatePtr;
public:
A(aaaaa); // не инлайновый, определён после определения Private, делает m_private(new PrivateA(aaaaa))protected:
A(PrivatePtr&& p) : m_private(p) {}
PrivateA& privateA() const { return *m_private; }
};
class B : public A
{
class PrivateB; // : public A::PrivateApublic:
B(bbbbb); // не инлайновый, определён после определения Private, делает : A(PrivatePtr(new PrivateB(bbbbb))) {}protected:
PrivateB& privateB() const { return static_cast<PrivateB&>(privateA()); }
};
Здравствуйте, AcidTheProgrammer, Вы писали:
ATP>Здравствуйте, Кодт, Вы писали:
К>>Если конструктор A прозрачный, т.е. вызывает конструктор A::Private с теми же аргументами, что переданы в A, то всё достаточно просто. ATP>В вашей терминологии он прозрачный, да
К>>
ATP>Вот тут и проблема, у меня, по задумке, указатели в B и A должны указывать на один и тотже объект (поэтому и shared_ptr), но на разные уровни в иерархии Private. А... даже если пока упростить и рассмотреть ваш вариант: я не понимаю как у вас теперь достучаться в B::Private из B класса? Или я туплю?
Поскольку у тебя и в А-классе и в В-классе присутствует реализация, то архитектурно это похоже больше на декоратор. Такой приём наследования конкретных классов (декоратор) применяется в том случае, если ты пытаешься использовать функционал базового А-класса, но тебе необходимо перед/после вызова А-методов() вызвать В-метод(). Либо, если у тебя нет доступа к модификации стороннего класса.
Короткое отступление, лучше называть B -- base class and D -- derived class.
Если ориентироваться на GoF, то в брижде будут взаимодействовать абстрактные классы. Соответственно, бридж настраивается просто через указатель на заранее инстанцированный класс реализации. Можно и динамически в процессе работы проинициализировать указатель на реализацию.
Хотелось бы предостеречь тебя от вычурности с таким наследованием и вложенностью классов. То, что неудобно проинициализировать -- это первый звоночек. Может есть более простое и элегантное решение получить то, что ты задумал? Извини, что только предостерёг, но на вопрос прямо не ответил.
Да у меня примерно такая реализация как у вас и получается. shared_ptr тут, чисто, для иллюстрации что Private расщаривается между A и B, я за него не держусь...
Я понимаю что можно привести вверх типы, но это именно то, чего я пытался избежать. Что я называю некрасивым:
В конструкторе B появляется тип B::Private и вот, он — есть, бери и присваивай. Но он "спускается" полиморфно в класс A, а затем его нужно доставать от туда, используя dynamic_cast. А как же статическая проверка типов?
А потом а если я не хочу в реализации использовать virtual методы....? В общем надеялся что такую простую задачу можно решить не теряя информацию о типах.
Спасибо за предостережение. Я понимаю что есть абстрактные паттерны, но на практике бывают случаи где паттерны не всегда используются в чистом виде. Чаше как база, от которой можно отталкиваться и строить что-то более сложное. Я понимаю что данные сложности говорят о том что я что-то делаю неправильно. Если бы не понимал, я бы не задавал вопрос в форуме. Но все равно спасибо за ответы.
Здравствуйте, AcidTheProgrammer, Вы писали: ATP>Здравствуйте, _smit, Вы писали: _>>Skipped... ATP>Спасибо за предостережение. Я понимаю что есть абстрактные паттерны, но на практике бывают случаи где паттерны не всегда используются в чистом виде. Чаше как база, от которой можно отталкиваться и строить что-то более сложное. Я понимаю что данные сложности говорят о том что я что-то делаю неправильно. Если бы не понимал, я бы не задавал вопрос в форуме. Но все равно спасибо за ответы.
Согласен, правильно делаешь, что спрашиваешь. Просто, глядя на код, возникает ощущение, что "истина где-то рядом". Хорошее решение часто выкристаллизовывается в процессе кодирования и последующего рефакторинга (примеры из Роберт К. Мартин "Быстрая разработка программ"). Многие даже предостерегают от преждевременного утверждения архитектуры, например, нравится шутка про специалиста из: http://www.insidecpp.ru/art/38/.
Есть ещё один способ, двигаться не от реализации к использованию, а попробовать наоборот, от использования к реализации -- аналог TDD. То есть, как ты (или коллеги) будут использовать твое решение. А то убьёш кучу времени, сделаешь реализацию, а пользоваться неудобно. Это как раз причина, по которой я не смог подсказать, как инициализировать конструкторы, т.к. я не понимаюпредставляю, как и для чего будут использоваться твои классы. Посмотри на правило 32 http://www.rsdn.ru/res/book/cpp/cppstandards.xml
(здесь можно посмотреть полный текст). Это конечно не аксиома, а лишь правило, но прислушаться стоит, т.к. у тебя конкретные классы используются в качестве базовых, конфликт.
Можно описать исходные условия и как ты видишь реализацию, если это не коммерческая тайна конечно, а мы попробуем совместно что-то от себя посоветовать. Бывает, когда сформулируешь, задачу, то и ответ сам приходит
Здравствуйте, AcidTheProgrammer, Вы писали:
ATP>Да у меня примерно такая реализация как у вас и получается. shared_ptr тут, чисто, для иллюстрации что Private расщаривается между A и B, я за него не держусь... ATP>Я понимаю что можно привести вверх типы, но это именно то, чего я пытался избежать. Что я называю некрасивым: ATP>В конструкторе B появляется тип B::Private и вот, он — есть, бери и присваивай. Но он "спускается" полиморфно в класс A, а затем его нужно доставать от туда, используя dynamic_cast. А как же статическая проверка типов?
Зачем dynamic_cast, если static_cast?
Или там виртуальное наследование и всякие страсти заранее предполагаются?
ATP>А потом а если я не хочу в реализации использовать virtual методы....? В общем надеялся что такую простую задачу можно решить не теряя информацию о типах.
И не нужны тут виртуальные функции. Почти не нужны, потому что удалять реализацию придётся полиморфно. А этот полиморфизм придётся или в реализацию засовывать, в виде виртуального деструктора, или в указатель, в виде checked_delete, или в класс-фасад A и B, в виде, опять же, виртуального деструктора.
Здравствуйте, Кодт, Вы писали:
К>Зачем dynamic_cast, если static_cast? К>Или там виртуальное наследование и всякие страсти заранее предполагаются?
A<---B (B наследует реализацию A и добавляет что-то своё). A::Private<---B::Private (Private от B наследует реализацию Private от A и добавляет что-то своё). Теперь я к конструкторе класса B создаю B::Private и передаю конструктору класс A. Но в B мне также нужна "ссылка" на этот же B::Private класс. Как её получить без dynamic_cast из указателя A::Private? Или у меня ошибка здесь и вы предлагаете другое?
Здравствуйте, AcidTheProgrammer, Вы писали:
ATP>Спасибо за предостережение. Я понимаю что есть абстрактные паттерны, но на практике бывают случаи где паттерны не всегда используются в чистом виде. Чаше как база, от которой можно отталкиваться и строить что-то более сложное. Я понимаю что данные сложности говорят о том что я что-то делаю неправильно. Если бы не понимал, я бы не задавал вопрос в форуме. Но все равно спасибо за ответы.
Да тут не какой-то абстрактный ооп-паттерн, а скорее идиома конкретного языка, а именно сиплюсплюсный пимпл.
Почему пимпл актуален именно для С++?
Потому что в других языках модульная система и организация памяти даёт достаточные механизмы для абстрагирования, а в плюсах с их огромным количеством инлайнов (включая встраивание тела объекта в память на стеке и в тело объекта-владельца — это ведь тоже инлайн-информация, по сути) приходится выкручиваться, достраивая необходимое на лету.
Итак, пимпл решает 5 основных задач:
— сделать объект-фасад фиксированного типа (так же, как эту задачу решают и фиксированные указатели на полиморфные типы)
— сделать объект-фасад фиксированного и небольшого размера (опять же, указатели делают то же самое)
— сделать простую для пользователя фабрику реализации (указатель, будучи примитивным типом, инициализируется как Ptr p(new Obj(blablablablabla)), тогда как Pimpl p(blabla))
— скрыть ненужные зависимости, разорвать кольцевые зависимости (класс реализации можно определить позже декоратора, вплоть до другой единицы трансляции)
— скрыть ненужные детали реализации
Вопрос: а что из этого тебе реально потребовалось?
Вполне возможно, что можно обойтись пимплом для бедных: shared_ptr<InterfaceOfA> и двумя функциями-фабриками makeA(aaa), makeB(bbb).
Это я так, для примера. Возможно, что и невозможно в конкретном случае :):
ATP>A<---B (B наследует реализацию A и добавляет что-то своё). A::Private<---B::Private (Private от B наследует реализацию Private от A и добавляет что-то своё). Теперь я к конструкторе класса B создаю B::Private и передаю конструктору класс A. Но в B мне также нужна "ссылка" на этот же B::Private класс. Как её получить без dynamic_cast из указателя A::Private? Или у меня ошибка здесь и вы предлагаете другое?
Внимание, фокус
// накидай в тела классов всякую дребедень по вкусуclass Foo { ..... };
class Bar : public Foo { ..... };
int main()
{
Bar* b = new Bar;
Foo* f = b;
assert( static_cast<Bar*>(f) == b ); // только если нет виртуального наследования
assert( dynamic_cast<Bar*>(f) == b ); // только при наличии в Foo виртуальных методов
assert( reinterpret_cast<Bar*>(f) == b ); // самое стрёмное место: компилятор может сделать сдвиг базы, static_cast его учитывает, reinterpret - нет
}
Здравствуйте, Кодт, Вы писали:
К>Внимание, фокус К>
К>// накидай в тела классов всякую дребедень по вкусу
К>class Foo { ..... };
К>class Bar : public Foo { ..... };
К>int main()
К>{
К> Bar* b = new Bar;
К> Foo* f = b;
К> assert( static_cast<Bar*>(f) == b ); // только если нет виртуального наследования
К> assert( dynamic_cast<Bar*>(f) == b ); // только при наличии в Foo виртуальных методов
К> assert( reinterpret_cast<Bar*>(f) == b ); // самое стрёмное место: компилятор может сделать сдвиг базы, static_cast его учитывает, reinterpret - нет
К>}
К>
— я так понимаю, все проходит корректно так как первые две строчки main — рядом. А если:
B::B(new A)
{
m_private = static_cast<B::Private*>(A::GetPrivate()); // Возвращаем m_private из A
}
Здравствуйте, AcidTheProgrammer, Вы писали:
ATP> — я так понимаю, все проходит корректно так как первые две строчки main — рядом. А если: ATP>B::B(new A) ATP>Такое разве прокатит?
А вот нечего копировать A в B.
Копировать можно базу в базу, наследника в наследника, и с рядом оговорок — наследника в базу (при этом возникает срезка, иногда — с катастрофическими последствиями, иногда — полностью безопасно).
Ну а так, если ты знаешь тип источника (>B и >B::Private, пусть для определённости это будут C и C::Private), тип приёмника (B и B::Private),
то статическое приведение C::Private* к A::Private* (в момент создания источника) и обратно к B::Private* (при копировании) эквивалентно приведению C::Private* к B::Private*.
А уж если тип источника и приёмника совпадает, то сам бог велел!
Здравствуйте, AcidTheProgrammer, Вы писали:
ATP>Здравствуйте, Кодт, Вы писали:
К>>Внимание, фокус К>>
К>>// накидай в тела классов всякую дребедень по вкусу
К>>class Foo { ..... };
К>>class Bar : public Foo { ..... };
К>>int main()
К>>{
К>> Bar* b = new Bar;
К>> Foo* f = b;
К>> assert( static_cast<Bar*>(f) == b ); // только если нет виртуального наследования
К>> assert( dynamic_cast<Bar*>(f) == b ); // только при наличии в Foo виртуальных методов
К>> assert( reinterpret_cast<Bar*>(f) == b ); // самое стрёмное место: компилятор может сделать сдвиг базы, static_cast его учитывает, reinterpret - нет
К>>}
К>>
ATP> — я так понимаю, все проходит корректно так как первые две строчки main — рядом. А если:
ATP>
ATP>B::B(new A)
ATP>{
ATP>m_private = static_cast<B::Private*>(A::GetPrivate()); // Возвращаем m_private из A
ATP>}
ATP>
ATP>Такое разве прокатит?
Я в подобной ситуации делал так: существовало две параллельные иерархии классов, имплементационная со своими .h, .cpp "для своих" т.е. эти хедера наружу не светились, и интерфейсная в общем случае тоже с .h и .cpp.
Выглядело примерно так:
public.h
class A
{
public:
class impl; // поскольку impl не виден наружу можем смело делать его public
A(impl *ptr = nullptr); // если impl != nullptr запоминаем, иначе создаем
// методы, методы, методы...
};
class B : public A
{
public:
class impl;
B(impl *ptr = nullptr); // если impl != nullptr запоминаем, иначе создаем
// методы, методы, методы...
};
private.h
class impl::A
{
};
class impl::B : public impl::A
{
};
Но прежде чем такое делать, надо серьезно подумать о целесообразности. Кол-во файлов и геморроя умножается на 2
Здравствуйте, saf_e, Вы писали:
_>Но прежде чем такое делать, надо серьезно подумать о целесообразности. Кол-во файлов и геморроя умножается на 2
Убедили, убедили, я полностью изменил реализацию, спрямив кое-где углы используя частные особенности данных. Теперь у меня остался чисто программерский интерес, как токе, всё таки, можно сделать.
_>Я в подобной ситуации делал так: существовало две параллельные иерархии классов, имплементационная со своими .h, .cpp "для своих" т.е. эти хедера наружу не светились, и интерфейсная в общем случае тоже с .h и .cpp.
_>Выглядело примерно так:
_>
_>public.h
_>class A
_>{
_>public:
_> class impl; // поскольку impl не виден наружу можем смело делать его public
_> A(impl *ptr = nullptr); // если impl != nullptr запоминаем, иначе создаем
_>// методы, методы, методы...
_>};
_>class B : public A
_>{
_>public:
_> class impl;
_> B(impl *ptr = nullptr); // если impl != nullptr запоминаем, иначе создаем
_>// методы, методы, методы...
_>};
_>private.h
_>class impl::A
_>{
_>};
_>class impl::B : public impl::A
_>{
_>};
_>
Ну так это в точности тоже самое что и у меня . Это мне и нужно... А расскажите про самое интересное: если B::impl создавать снаружи B то, да проблем нет. Но я то, пытаюсь сделать чтобы B создавал impl в конструкторе. Тогда имеем:
B::B(...)
: A(new B::impl(...))
, m_imple(????) // Как сюда получить копию указателя ????
{
...
}
указатель может получить либо A, либо B. Этот вопрос можно как-то красиво решить?
Здравствуйте, AcidTheProgrammer, Вы писали:
ATP>Здравствуйте, saf_e, Вы писали:
ATP>Ну так это в точности тоже самое что и у меня . Это мне и нужно... А расскажите про самое интересное: если B::impl создавать снаружи B то, да проблем нет. Но я то, пытаюсь сделать чтобы B создавал impl в конструкторе. Тогда имеем: ATP>
ATP>B::B(...)
ATP>: A(new B::impl(...))
ATP>, m_imple(????) // Как сюда получить копию указателя ????
ATP>{
ATP>...
ATP>}
ATP>
ATP>указатель может получить либо A, либо B. Этот вопрос можно как-то красиво решить?
Странно, у меня на такое компилятор выдавал предупреждение, что я использую не инициализированную переменную . Я понимаю что ничего страшного, но предупреждения я давно перестал терпеть...
ATP>Странно, у меня на такое компилятор выдавал предупреждение, что я использую не инициализированную переменную . Я понимаю что ничего страшного, но предупреждения я давно перестал терпеть...
И мне странно, она со всех сторон инициализирована возможно у вас там что-то еще...