И делаем конкретные реализации Impl_base, в которых определяем функцию IsEqual().
class Impl_A: public Impl_base {
public:
virtual bool IsEqual(const Impl_base* pImpl) const;
{
const Impl_A *pImplA=dynamic_cast<const Impl_A*>(pImpl);
if (!pImplA) return false;
return m_a==pImplA->m_a;
}
private:
int m_a;
};
Смущает то, что, для решения такой, казалось бы простой задачи приходится использовать тяжелую артиллерию — RTTI.
Можно ли как-то сравнить PImpl-ы, не использую dynamic_cast<>(), typeid(), и уж тем более самопальные заменители RTTI ?
Здравствуйте, Аноним, Вы писали:
А>Смущает то, что, для решения такой, казалось бы простой задачи приходится использовать тяжелую артиллерию — RTTI. А>Можно ли как-то сравнить PImpl-ы, не использую dynamic_cast<>(), typeid(), и уж тем более самопальные заменители RTTI ?
class Impl_A: public Impl_base {
public:
virtual bool IsEqual(const Impl_base* pImpl) const;
{
return m_a==pImplA->GetA();
}
protected:
virtual int GetA() const
{
return m_a;
}
private:
int m_a;
};
Здравствуйте, Аноним, Вы писали:
А>Пусть есть базовый класс реализации A>... А>Смущает то, что, для решения такой, казалось бы простой задачи приходится использовать тяжелую артиллерию — RTTI. А>Можно ли как-то сравнить PImpl-ы, не использую dynamic_cast<>(), typeid(), и уж тем более самопальные заменители RTTI ?
Здравствуйте, Аноним, Вы писали: А>Можно ли как-то сравнить PImpl-ы, не использую dynamic_cast<>(), typeid(), и уж тем более самопальные заменители RTTI ?
как насчет двойной диспечеризации?
class Impl_A;
class Impl_base {
public:
virtual bool IsEqual(const Impl_base& other) const=0;
virtual bool IsEqual(const Impl_A& other) const=0;
};
class Impl_A: public Impl_base {
public:
virtual bool IsEqual(const Impl_base& other ) const;
{
return other.IsEqual(*this);
}
virtual bool IsEqual(const Impl_A& other) const=0
{
// compare somehow 2 Impl_A
}
};
Да, а вообще накой вам эта иерархия пиплов? ИМХО слишком сложно.
А>Смущает то, что, для решения такой, казалось бы простой задачи приходится использовать тяжелую артиллерию — RTTI. А>Можно ли как-то сравнить PImpl-ы, не использую dynamic_cast<>(), typeid(), и уж тем более самопальные заменители RTTI ?
А вы обяжите потомков Impl_base возвращать хеш (например, md5) вычисленный по состоянию объекта:
Здравствуйте, superman, Вы писали:
S>как насчет двойной диспечеризации?
Большое спасибо за пример. Когда-то читал про это, но совсем забыл. Теперь вспомнил, буду применять.
S>Да, а вообще накой вам эта иерархия пиплов? ИМХО слишком сложно.
class Impl_Animal {
public:
virtual void Run()=0;
};
class Impl_Cat: public Impl_Animal {
public:
virtual void Run() { /*something to do*/ }
};
class Impl_Dog: public Impl_Animal {
public:
virtual void Run() { /*something to do*/ }
};
class Impl_Mouse: public Impl_Animal { /*......*/ };
class Impl_Snake: public Impl_Animal { /*......*/ };
class Animal {
public:
void Run() { m_pImpl->Run(); }
private:
Impl_Animal *m_pImpl;
};
Итого, у нас все животные имеют одинаковый тип: Animal, тем не менее поведение у них разное. То есть, можно хранить разных зверей в одном контейнере.
Естественно, есть нюансы, связанные с копированием и разрушением этих объектов, но это уже другая тема
Здравствуйте, Аноним, Вы писали:
А>Очень интерессное решение, впервые такое вижу. А>Только я не заню, что такое Flat memory? Можете объяснить? Или направте, пожалуйста, к источнику.
Тип адресации такой, здесь или здесь можно почитать. Суть в том, чтобы адрес был полный, т.е. уникальный, чтобы являться ключом в операциях сравнения.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
У каждого класса есть своя uniqAddressVar. Если классы разные, то uniqAddressVar у них разные, и адреса uniqAddressVar тоже разные. А если классы одинаковые, то GetClassId() вернет адрес одного и того же uniqAddressVar.
Здравствуйте, ChainEG, Вы писали:
CEG>А почему не будет работать, если не Flat memory? CEG>У каждого класса есть своя uniqAddressVar. Если классы разные, то uniqAddressVar у них разные, и адреса uniqAddressVar тоже разные. А если классы одинаковые, то GetClassId() вернет адрес одного и того же uniqAddressVar. Здесь
В общем случае, это зависит от реализации адресации. К примеру, перед использованием указателей, надо указать сегмент/селектор памяти, тогда одним void* — не обойтись.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
V>В общем случае, это зависит от реализации адресации. К примеру, перед использованием указателей, надо указать сегмент/селектор памяти, тогда одним void* — не обойтись.
Еще раз прочитал Википедию. Ответ, видимо, кроется в середине статьи в следующих строчках:
Segmented memory model:
* .............
* Pages can overlap / poor resource protection and isolation
* Many to one address translation correspondence: Many segment:offset combinations resolve to the same physical address
То есть, в сегментной модели один и тот же адрес может быть представлен различными комбинациями сегмент:смещение. Выходит, два указателя p1 и p2, имеющие различные значения, могут указывать на один о тот же объект.
Но вот что говорит стандарт по поводу сравнения указателей:
3.9.2.4
A void* shall be able to hold any object pointer.
5.9.2
— If two pointers p and q of the same type point to the same object or function, or both point one past the end of the same array, or are both null, then p<=q and p>=q both yield true and p<q and p>q both yield false.
5.10.1.
Pointers to objects or functions of the same type (after pointer conversions) can be compared for equality. Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address (3.9.2).
Получается, C++ гарантирует, что p1==p2 тогда и только тогда, когда p1 и p2 указывают на один и тот же объект. И наплевать, что p1 и p2 имеют разное битовое представление. Это забота компилятора или процессора — понять, ссылаются ли эти указатели на один объект, или на разные, и вернуть мне true или false.
V>>В общем случае, это зависит от реализации адресации. К примеру, перед использованием указателей, надо указать сегмент/селектор памяти, тогда одним void* — не обойтись. CEG>Еще раз прочитал Википедию. Ответ, видимо, кроется в середине статьи в следующих строчках: CEG>
CEG>Segmented memory model:
CEG> * .............
CEG> * Pages can overlap / poor resource protection and isolation
CEG> * Many to one address translation correspondence: Many segment:offset combinations resolve to the same physical address
CEG>То есть, в сегментной модели один и тот же адрес может быть представлен различными комбинациями сегмент:смещение. Выходит, два указателя p1 и p2, имеющие различные значения, могут указывать на один о тот же объект.
..и наоборот. CEG>Но вот что говорит стандарт по поводу сравнения указателей: CEG>Получается, C++ гарантирует, что p1==p2 тогда и только тогда, когда p1 и p2 указывают на один и тот же объект. И наплевать, что p1 и p2 имеют разное битовое представление. Это забота компилятора или процессора — понять, ссылаются ли эти указатели на один объект, или на разные, и вернуть мне true или false.
Некоторые пункты могут и не выполняться под конкретным компилятором и платформой.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, Vain, Вы писали:
V>Работает если Flat memory.
А разве равенство указателей не гарантирует совпадение объектов в любой модели?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Для pimpl самого по себе — операция сравнения ничем не отличается от любой другой функции, засунутой в реализацию.
А для полиморфных объектов — тут, безусловно, мы попадаем на мультиметод.
Дальше возникают варианты, как сделать его
1) правильным, с т.з. предметной области
2) дешёвым
3) расширяемым (если множество классов не зафиксировано)
Правильность — это, в первую очередь, соблюдение аксиоматики операции сравнения:
— рефлексивность — x==x
— симметрия — x==y <=> y==x
— транзитивность — x==y && y==z <=> x==z
Для классов, особенно, участвующих в наследовании, требуется решить: сравнимы ли разнотипные объекты (из разных ветвей иерархии, а также предок с наследником).
Проще всего, когда неравенство типов влечёт неравенство объектов.
Тогда мы сравниваем rtti (встроенное или рукодельное) и, в случае равенства, делаем допущение
Но, скажем, если у нас есть прямоугольники и квадраты (кто от кого наследует, и наследует ли вообще — сейчас неважно).
Естественно считать, что прямоугольник со сторонами X,X равен квадрату со стороной X.
А все фигуры с нулевыми размерами — вообще равны между собой.
Реализовать мультиметод можно кучей разных способов. Наиболее популярный — двойная диспетчеризация, хотя для обеспечения симметрии там придётся потратить силы.
Если множество классов невелико, и они не разбросаны по системе, то я бы предпочёл сделать паттерн-матчинг.
Здравствуйте, Vain, Вы писали:
V>В общем случае, это зависит от реализации адресации. К примеру, перед использованием указателей, надо указать сегмент/селектор памяти, тогда одним void* — не обойтись.
Да просто будет void far*, обеспечивающий глобальную уникальность адреса — а void near* (только смещение без селектора) это для жмотов, экономящих на спичках.
Под досом в large модели жили же как-то?
Здравствуйте, Кодт, Вы писали:
К>Под досом в large модели жили же как-то?
А разве там можнобыло сравнить два укащателя на разные сегменты и состоящие только из смещения...
По идее, если можешь указатели разыменовать, то можешь и сравнить...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском