LINUX.ORG.RU

Type casting в c++.

 


0

2

Сегодня столкнулся с тем, что для моего проекта на плюсах просто необходимо преобразование типов как вверх, так и вниз (большая часть объектов хранится в одном stl контейнере, хранящем указатели имеющие тип интерфейса, от которого наследуется добрая половина всего, без кастования типов пришлось бы переносить некоторый функционал в сам интерфейс, а это еще больший говнокод)

Допустим, у нас есть такой код.

// Предположим,что класс Foo абстрактный                    
// (иначе downcast попросту невозможен)

class Foo {
   // какая-то переменная
   SomeType something;
   // что-то еще
public:
   // меняем вышеназванную переменную
   virtual void setSomething(SomeType newSomething);
   // что-то еще
};

class Bar : public Foo{
   // что-то
public:
   void setSomething(SomeType newSomething) override;
   // получаем значение 
   SomeType getSomething();
};

int main(){
   // создаем указатель, выделяем память и задаем параметр
   Bar * somepointer_a = new bar;
   somepointer_a->setSomething(SomeType alpha);
   //Создаем новый указатель с типом родительского класса через \
   //кастование типов
   Foo * somepointer_b = dynamic_cast<Foo*>(someponter_a);
   //Используем  функцию, которая была оверрайднута
   somepointer_b->setSomething(SomeType beta);
   //Создаем указатель изначального типа и получаем значение
   Bar * somepointer_c = dynamic_cast<Bar*>(someponter_b);
   std::cout << somepointer_c->getSomething();
}

Есть ли вероятность, что на stdout выведет не то, что было в beta, а то что было в alpha или вообще вылетит с ошибкой? А если еще конктретнее, можно ли это считать неопределенным поведением и способна ли последовательность из upcast и downcast привести к потере данных?



Последнее исправление: le_poisson (всего исправлений: 5)

а это еще больший говнокод

Вообще говоря, каст к потомку это и есть говнокод. Надо писать так, чтобы касты были только к родителям и с помощью static_cast.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

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

le_poisson
() автор топика

// оверрайдим функцию void setSomething(SomeType newSomething);

Добавь ключевое слово override, комментарий излишен.

ox55ff ★★★★★
()

somepointer_a->setSomething(SomeType alpha);

У тебя тут setSomething из bar вызовется даже без каста. Метод-то виртуальный.

anonymous
()
Ответ на: комментарий от le_poisson

перегруженная в классе-потомке и подобные возможные источники неопределенности.

Всё будет работать, если не перепутал переопределение и перекрытие. Всегда пиши override, когда переопределяешь. Это позволит выловить проблему на этапе компиляции.

Только в dynamic_cast передавай валидный указатель. Если там мусор, то будет UB.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

Хорошо, спасибо. Мусор туда передаваться не должен, указатели попадают в список только одним образом, а по удалении из контейнера память освобождается, по крайней мере при удалении задуманным способом (хотя в функцию, где происходит преобразование, также можно попасть только из списка, так что даже если останется мусор, это не должно повлиять на кастование).

le_poisson
() автор топика
Ответ на: комментарий от le_poisson

Зачем кастовать указатели, если методы виртуальные? Все равно результат будет один и тот же - вызов метода производного класса.

anonymous
()
Ответ на: комментарий от anonymous

Данные хранятся под типом класса-родителя (от которого наследуется еще несколько классов). У этих классов есть свои методы, отличные друг от друга и включающие в себя не только переопределение виртуальных методов родителя, поэтому кастование типов необходимо, чтобы можно было вызывать некоторые методы наследника.

В общем пример не очень, но суть мне уже пояснили.

le_poisson
() автор топика

Во первых:

// это дерьмо собачье
Foo * somepointer_b = dynamic_cast<Foo*>(someponter_a);

// Нужно
Foo *somepointer_b = somepointer_a;

И это примитивные основы, на которые ты забил.

Во-вторых. В С++ указатели, наследование, функции преобразования типов и прочее существует не для того, чтобы тупо кастить туда сюда указатели.

Дизайн должен быть простым. Такой херни не должно быть.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

а) Какая разница, как приводить указатель к типу родителя, если все равно придется кастовать через dynamic_cast? Сложность что так, что так О(1), плюс dynamic_cast вносит свои ограничения, например, что методы должны быть виртуальными.

б) Какую альтернативу ты предлагаешь: перенести все данные/методы в родительский класс, раздув его раза в 4 в исходниках и неизвестно во сколько в плане потребления памяти на экземпляр (некоторые наследники и наследники наследников содержат свои контейнеры stl) или на каждый класс создать свой список со своими указателями, раздув уже основной объект?

le_poisson
() автор топика

в ООП, явный каст на предка не нужен. потому что это основа ооп.

потому что, раз все поля предка есть есть в потомке - то указатель типа предок -> на обьект потомок, валиден по умочанию - для этого ооп и задумывалось.

виртуальный метод все равно будет вызываться переопределенный. и это вторая базовая задумка ооп. чтобы вызвать виртуальный метод предка, надо тогда писать имя класса, типа Foo::method.

опять же надо понимать, что виртуальные методы у одного и того же обьекта разные, во время выполнения конструктора и деструктора. при конструировании вызывается тот вирт метод, что определен в данный момент в иерархии, иерархия идет от базы к потомкам. то есть если потомок переопределяет метод, то в конструкторе предка этого еще не видно, и там вызывается тот метод, что видит конструктор. то есть или свой, или своего уже предка. при деструкции - все идет в обратном порядке.

простыми словами, конструктор обьекта всегда выполняется идентично для обьекта данного класса, и никакой потомок, если он есть, не может это поведение переопределить своим перегруженным методом.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от le_poisson

Какая разница, как приводить указатель к типу родителя, если все равно придется кастовать через dynamic_cast? Сложность что так, что так О(1), плюс dynamic_cast вносит свои ограничения, например, что методы должны быть виртуальными.

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

Окей. Я уже понял, что смысла динамическое кастование тут не несет, но пока до меня не дошло, почему это плохо, помимо возможного усложнения читаемости.

le_poisson
() автор топика
Ответ на: комментарий от le_poisson

Какую альтернативу ты предлагаешь: перенести все данные/методы в родительский класс, раздув его раза в 4 в исходниках и неизвестно во сколько в плане потребления памяти на экземпляр (некоторые наследники и наследники наследников содержат свои контейнеры stl) или на каждый класс создать свой список со своими указателями, раздув уже основной объект?

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

alysnix ★★★
()
Ответ на: комментарий от le_poisson

Я уже понял, что смысла динамическое кастование тут не несет, но пока до меня не дошло, почему это плохо, помимо возможного усложнения читаемости.

потому что динамик каст это каст строго вниз по иерархии, поскольку там может быть обьект не того класса. а вот вверх - это автоматом совместимо

alysnix ★★★
()
Ответ на: комментарий от alysnix

Я вообще создал эту тему, так как хотел хранить множество различных объектов в одном списке приведенных к типу предка, но в процессе оказалось, что в некоторых случаях необходим обратный каст.

le_poisson
() автор топика
Ответ на: комментарий от le_poisson

а) Какая разница, как приводить указатель к типу родителя, если все равно придется кастовать через dynamic_cast? Сложность что так, что так О(1), плюс dynamic_cast вносит свои ограничения, например, что методы должны быть виртуальными.

А если твой код будет в составе алгоритма, который имеет хоть линейную сложность, а dynamic_cast - все же времени стоит, то из-за того, что ты поленился несколько глав почитать, будут бессмысленные тормоза.

dynamic_cast вносит свои ограничения, например, что методы должны быть виртуальными

Чего, чего. Да у предка и наследника может вообще не быть методов, dynamic_cast отработает. Или ты что другое имел сказать?

б) Какую альтернативу ты предлагаешь: перенести все данные/методы в родительский класс, раздув его раза в 4 в исходниках и неизвестно во сколько в плане потребления памяти на экземпляр (некоторые наследники и наследники наследников содержат свои контейнеры stl) или на каждый класс создать свой список со своими указателями, раздув уже основной объект?

Как dynamic_cast херачить - так норм, а тут ты про производительность вспомнил.

Не предлагай мне взамену одного говнокода другой. Какая вообще глобальная задача. Есть вероятность, что все это можно написать проще.

rumgot ★★★★★
()
Ответ на: комментарий от le_poisson

но в процессе оказалось, что в некоторых случаях необходим обратный каст.

и это правильно, поскольку обьекты разных потомков, и ты строго говоря не знаешь, имея указатель на базу, на какой в реальности обьект он указывает, у тебя ж в списке они разнородные. на самом деле если ты строго уверен, что каст пройдет, то есть уверен в типе обьекта потомка, по какому-то тегу, ты может кастовать статически.

class A{

};

class B: public A{

};

void ff(){
	A* lp = new B;
	B* lpp = static_cast<B*>(lp);
};

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

В Qt например так сделано (QEvent). Но это же слишком нормальный способ, неинтересно, нет духа приключений.

rumgot ★★★★★
()
Ответ на: комментарий от alysnix

Окей, спасибо. Я вообще тут подумал переделать список в словарь, где первым в паре будет объект, одновременно являющийся тегом класса и фабрикой (один из методов создает объект класса по аргументам), а вторым - собственно список с объектами

le_poisson
() автор топика
Ответ на: комментарий от le_poisson

Ну или прямое указание на класс. Посмотрим, что получится лучше реализовать.

le_poisson
() автор топика
Ответ на: комментарий от le_poisson

*Объектами одного наследуемого класса, да.

le_poisson
() автор топика
Ответ на: комментарий от le_poisson

я вот так обычно делаю, типа свой динамический каст и ошибки видно.

class B;
class C;

class A{
protected:
	enum Kind { __A, __B, __C};
	Kind _kind;

public:	
	A():_kind(__A) {}
	A(Kind fkind):_kind(fkind){}

public:
	///функции каста - сами напишем типа "динамический каст"
	B* as_B();
	С* as_C();
};
//------------------------------------------

class B: public A{
public:
	B():A(__B) {}
};

class C: public A{
public:
	C():A(__C) {}
};

///функции каста - сами напишем типа "динамический каст"
B* A::as_B(){  
	if(_kind==__B) return static_cast<B*>(this);
	return nullptr; ///тут можно и принтануть, ибо ошибка каста
}
	
///функции каста
C* A::as_C(){  
	if(_kind==__C) return static_cast<C*>(this);
	return nullptr;
}
//------------------------------------------

void ff(){
	A* lp = new B;
	B* lpp = lp->as_B(); ///работает
	if(lp->as_C() == nullptr) error("WRONG CAST TO <C>");
};
alysnix ★★★
()
Ответ на: комментарий от alysnix

Это тоже самое, что ты описал уже, но с запрятыванием этой логики внутрь. И это никакой не аналог того, как работает dynamic_cast.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

И это никакой не аналог того, как работает dynamic_cast.

да ты шо! в данном случае выполняются все нужности динамик каста, но дешевыми средствами. динамик каст в общем случае требует rtti, а мы хотим все сделать дешево и сердито.

понятно, что даже если погромист пишет динамик каст, компилятор все равно попытается его свести к статическому, и реальный динамик будет испускать только в случае, если это не получается. но rtti все равно нужен.

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от alysnix

да ты шо!

Да я шо.

Твой динамический каст как интересно будет работать в случае виртуального наследования?

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Твой динамический каст как интересно будет работать в случае виртуального наследования?

а мы говорили о виртуальном наследовании??? мы разбирали случай невиртуального наследования и как тут проще и надежней писать.

где я сказал, что статик каст вообще заменяет динамик? если б оно было так, динамик был бы не нужен вовсе

alysnix ★★★
()
Ответ на: комментарий от alysnix

__B

Имя относится к зарезервированным: подчёркивание и большая буква или двойное подчёркивание.

ox55ff ★★★★★
()
Ответ на: комментарий от alysnix

мы разбирали случай невиртуального наследования и как тут проще и надежней писать

Я не спорю, что в данном случае лучше использовать этот способ. Или о том, что это хороший способ.

Я пишу, что неверно это называть динамическим кастом.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Я пишу, что неверно это называть динамическим кастом.

для данного случая это типичный динамический каст, поскольку есть проверка преобразования.

динамический каст присутствует в любом языке с наследованием, поскольку преобразование от базы к потомку может быть некорректным. в моем коде проверка корректности преобразования есть. и то что я написал, ПОЛНОСТЬЮ заменяет динамический каст от компилятора в данном случае. об этом и был разговор - как сделать дешево и сердито.

и смыслом динамического каста является именно проверка преобразования. и не надо зацикливаться на плюсах с его виртуальными базами.

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

случай с виртуальной базой не рассматриваем.

вот тут кастуем от разных баз статическим кастом на наследников.

class A1{ };
class A2{ };

class B: public A1, public A2 {
};

///test if static cast works
void test_cast(A1 *a1, A2 *a2){
	B *b1 = static_cast<B*> (a1);
	B *b2 = static_cast<B*> (a2);
}

void ff(){
	A1 *a1 = new B; /// to base A1
	A2 *a2 = new B; /// to base A2
	test_cast(a1,a2);
}

код с тегами классов функционально аналогичен rtti инфе, просто тут вся эта инфа сведена к тегу прямо внутри обьекта класса. и по ней делается динамическая проверка. то есть функционально это динамический каст по самодельной ртти инфе.

alysnix ★★★
()
Ответ на: комментарий от ox55ff

[quote]dynamic_cast кинет исключение std::bad_cast, если нельзя привести к указанному типу.[/quote]

Там RAW поинтеры поэтому исключения не будет, будет NULL

zaz ★★★★
()
Последнее исправление: zaz (всего исправлений: 1)
Ответ на: комментарий от alysnix

динамический каст присутствует в любом языке с наследованием, поскольку преобразование от базы к потомку может быть некорректным. в моем коде проверка корректности преобразования есть. и то что я написал, ПОЛНОСТЬЮ заменяет динамический каст от компилятора в данном случае. об этом и был разговор - как сделать дешево и сердито.

и смыслом динамического каста является именно проверка преобразования. и не надо зацикливаться на плюсах с его виртуальными базами.

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

Нахера ты это пишешь. Вот ты среди ответу еще херову гору лишней информации фигачишь.

Ладно.

случай с виртуальной базой не рассматриваем

Конечно, ведь этот случай твой динамический каст не покрывает.

Короче. Нехер вводить путаницу в термины, а потом оправдываться.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Короче. Нехер вводить путаницу в термины, а потом оправдываться.

але, каким волшебным образом из моего коммента в примере - вот он

///функции каста - сами напишем типа "динамический каст"

в котором есть два хинта - «типа», и кавычки, ты сделал вывод, что я решил покрыть простым образом всю функциональность динамического каста плюсов???

тебе уж в третий-пятый раз говорят, что рассматривается данный конкретный случай, и как его оформить ТСу.

тебе сказали, что суть динамического каста даже в плюсах - в ПРОВЕРКЕ корректности некого преобразования, форма которого не оговаривается, к указателю(ссылке) на некий желаемый тип. а не в том, что там «кручу-верчу - запутать хочу» с виртуальными базами.

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

что по твоему - динамический каст. дай определение уж наконец тогда.

и вообще прежде чем учить жизни - давай определения, а то непонятно что за тараканы у тебя в голове и с чем спорить.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

тебе сказали, что суть динамического каста даже в плюсах - в ПРОВЕРКЕ корректности некого преобразования, форма которого не оговаривается, к указателю(ссылке) на некий желаемый тип. а не в том, что там «кручу-верчу - запутать хочу» с виртуальными базами.

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

Зачем ты столько воды пишешь? Ты мне что, экзамен стаешь?

але, каким волшебным образом из моего коммента…

А за каким нужно спорить с комментом:

И это никакой не аналог того, как работает dynamic_cast.

Потому что твой вариант не покрывает случая, когда наследование виртуальное.

что по твоему - динамический каст. дай определение уж наконец тогда

Динамический каст (это твой термин) - это ты взял из оператора dynamic_cast. Так вот основываясь на этом динамический каст - это, внезапно, оператор dynamic_cast ни больше ни меньше. И называть этим другие сущности - вот это мне и режет слух.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от rumgot

Динамический каст (это вой термин) - это взял из оператора dynamic_cast. Так вот основываясь на этом динамический каст - это, внезапно, оператор dynamic_cast ни больше ни меньше. И называть этим другие сущности - вот это мне и режет слух.

это не определение. давай определение, что такое по твоему динамический каст, в чем его смысл. я его дал уже раза три. это не текст «dynamic_cast», как ты тут пишешь. в чем конкретно суть этого оператора?

Потому что твой вариант не покрывает случая, когда наследование виртуальное.

у тебя порядок с когнитивными способностями? тебе сказали сто раз, что этот случай не рассматривается, поскольку у ТС вопрос не про это. зачем повторять то, на что уже было неоднократное уточнение?

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

Твое утверждение:

«Способ через static_cast + проверка типа по специальному члену» - это типа свой динамический каст.

Мое утверждение:

Это никакой не аналог того, как работает dynamic_cast, потому что это не покрывает случай с виртуальным наследованием.

А ты споришь, что этот аргумент нельзя, потому что в теме нет обсуждения виртуального наследования.

Ну и что с того. Я отвечаю не ТСу, я делаю замечание тебе на твое утверждение. А в твоем утверждении НЕТУ ОГРАНИЧЕНИЯ на то, что это ТОЛЬКО для случаев, когда НЕТУ ВИРТУАЛЬНОГО НАСЛЕДОВАНИЯ.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Твое утверждение: «Способ через static_cast + проверка типа по специальному члену» - это типа свой динамический каст.

вранье. там было утверждение - что для данного способа наследования. не надо обобщать частные утверждения.

да и как бы я написал компилируемый код, если б там было наследование виртуальное? там написан компилируемый код, для рассматриваемого вопроса. я предложил делать так, поскольку ТС сомневался в том, что dynamic_cast ему даст большие накладные расходы.

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

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

Ну простите пожалуйста. Я не утверждал, что этот способ плох. Я просто… А ладно, убедил.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)

необходимо преобразование типов как вверх, так и вниз

Это дурно пахнет. Иногда конечно необходимо, но лучше не надо.

Сама идея разветвлять логику в зависимости от того, какой конкретно тип достался антипаттерн. Различное поведение для разных типов должно реализоваться в самих Foo и Bar (динамический полиморфизм), а логика их использующая должна одинаково работать и с Foo, и с Bar.

Рекомендую также посмотреть на шаблон visitor.

без кастования типов пришлось бы переносить некоторый функционал в сам интерфейс, а это еще больший говнокод

Зачем в интерфейс? Можно в базовый класс отнаследованный от интерфейса. И не такой уж страшный говнокод наличие части реализации в интерфейсе. Уж точно не хуже кастования типов и ветвления от этого логики.

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 1)
Ответ на: комментарий от dvetutnev

Сама идея разветвлять логику в зависимости от того, какой конкретно тип достался антипаттерн.

глубочайшее заблуждение. есть стопудовый паттерн, оно же требование к правильному дизайну классов. а именно -

класс не должен иметь свойств, которые ему не свойственны.

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

то есть статически такие свойства будут доступны и их использование не обнаруживается компилятором.

например если принтеры и сканеры обьединить в общую иерархию с базовым классом с двумя свойствами - print и scan.. то получится, что принтер имеет свойство scan, а сканер - print. и можно сканеру сказать print, что есть очевидная ошибка.

отсюда видно что у них не должно быть общего предка с такими свойствами и это неверная иерархия.

вывод - преобразование типа к потомку - необходимо, чтобы не нарушать принцип - обьект класса должен уметь только «свои свойства», но не свойства каких-то других классов, навязанные ему предковыми классами.

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

например если принтеры и сканеры обьединить в общую иерархию с базовым классом с двумя свойствами - print и scan.. то получится, что принтер имеет свойство scan, а сканер - print. и можно сканеру сказать print, что есть очевидная ошибка.

Но-но-но. Я не предлагал искусственно вводить общую база. По условиям задачки объекты находятся в одном контейнере, общая база у них уже есть и не нужно принтеры со сканерами смешивать.

Если же объекты в контейнере настолько различны, то это уже изначально плохой дизайн и проблема не имеет смысла.

dvetutnev
()
Ответ на: комментарий от dvetutnev

Если же объекты в контейнере настолько различны, то это уже изначально плохой дизайн и проблема не имеет смысла.

как не имеет? мы просто пытались сложить обьекты разных классов в одно хранилище. или нарисовать некий общий предковый класс для всех других классов.

по вашему обьекты совсем разных классов не могут быть в одном списке например? такое нигде не написано. более того - могут, и необходимо преобразование к актуальному типу, при их оттуда извлечении, чтобы их использовать.

alysnix ★★★
()
Ответ на: комментарий от alysnix

Вся эта хрень не нужна вообще. Какие то базовые классы-помойки, общие ненужные свойства, методы кастов.

Когда можно забить на ООП-костыли и делать


class Printer
{
public:
	void print() const;
};

class Scanner
{
public:
	void scan() const;
};

class Foo
{
public:
	void print() const;
	void scan() const;
};

using Object = std::variant<Printer, Scanner, Foo>;
// используем гетерогенную коллекцию таких объектов
void bar(const std::vector<Object>& objects)
{
	auto call = template<class T>[](T&& o)
	{
		if constexpr(requires { std::declval<T>.print(); })
		{
			o.print();
		}
		if constexpr(requires { std::declval<T>.scan(); })
		{
			o.scan();
		}
	}
	for(auto& obj : objects)
		std::visit(call, obj);

}
CatsCantFly
()
Последнее исправление: CatsCantFly (всего исправлений: 3)
Ответ на: комментарий от alysnix

по вашему обьекты совсем разных классов не могут быть в одном списке например?

В чем профит такого дизайна? У меня не получается представить задачу, где это может потребоваться.

dvetutnev
()
Ответ на: комментарий от dvetutnev

В чем профит такого дизайна? У меня не получается представить задачу, где это может потребоваться.

любое хранилище например именованных обьектов. предок общий.

class NamedItem{
  string _name;
}

прменяется где угодно. например область видимости(scope) в компиляторе - хранилище разнородных именованных обьектов - констант, переменных, типов, функции и так далее.

обьектные базы данных - они вообще могут хранить разные обьекты.

сериализация - разнородные обьекты десериализуются из канального представления в некие актуальные типы.

любая сложная иерархия классов от одной базы, если их надо собирать в какие-то связанные структуры. типа списков, деревьев и прочее.

alysnix ★★★
()
Ответ на: комментарий от CatsCantFly

using Object = std::variant<Printer, Scanner, Foo>;

это хранение с тегом типа, о чем я и говорил. только спрятанное в мега_типа_крутой темплейт.

потом по тегу динамически выбирается правильный вариант call. по сути это вызов «виртуального метода» через задний проход, только больше по коду и совершенно нечитабельно.

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

и потом . для принтера и сканера в одном флаконе команда и принтанет и отсканит за один вызов… но зачем такое надо непонятно. или принтить или сканить оно должно

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.