在 c++ 中,通常比较继承层次结构中的对象
Generically comparing objects in an inheritance hierarchy in c++
我打算写一个这样的多映射
std::multimap <key, base_ptr> mymap;
我希望能够存储许多派生类(例如Der1
,Der2
)的指针,这些派生类从基派生。
现在,当我尝试将对象插入地图时,我首先对键进行查找,然后我需要比较该对象是否等效(不必是同一对象,因此不进行指针比较)与该位置的对象。因此,假设我覆盖了== operator
或编写了某种比较函数。现在,我想为此编写代码,以便在添加新的派生类时,我不必更改或添加任何内容。
所以我认为必须有一种通用的方法来写这个。但想不出一个。
我在想以下几件事
class Base
{
virtual Base * get() { return this; }
virtual bool isEqual(const Base& toObj) {
....
}
}
class Der1
{
Der1 * get() { return this; }
bool isEqual(const Der1& toObj) {
....
}
}
但这似乎也不起作用。 因为当我这样做时:
Base* bp1;
Base* bp2;
bp1->get()->isEqual(*(bp2->get()))
我看到对get()
的调用确实如我预期的那样最终出现在派生类的get()
中,但随后编译器将返回的值视为Base*
。这很可能是因为它是运行时多态性。但我发现很难相信不会有一种优雅而明显的方式来做到这一点。
有人可以建议。
你可以尝试这样的事情:
class Base
{
public:
// derived classes must implement this.
// the implementation is always the same code
virtual type_info getType() const = 0;
virtual bool isEqual(const Base& toObj) = 0;
}
class Der1 : Base
{
public:
type_info getType() const { return typeid (this); }
bool isEqual(const Base& toObj)
{
if (this.getType() == toObj.getType())
{
// we have 2 instances of Der1!
// do comparison here
}
else
{
// the other one is no Der1 => we are not equal
return false;
}
}
}
class Der2 : Base
{
public:
type_info getType() const { return typeid (this); }
bool isEqual(const Base& toObj)
{
if (this.getType() == toObj.getType())
{
// we have 2 instances of Der2!
// do comparison here
}
else
{
// the other one is no Der1 => we are not equal
return false;
}
}
}
void MyFunc()
{
Base* bp1;
Base* bp2;
// ...
// this should work now (although I have not tested it!)
bool theyAreEqual = bp1->isEqual(*bp2);
}
这很简单,请阅读此代码的注释:
class Dervived;
class Base
{
public:
/* Note:
* You can get rid of those functions and use dynamic_cast instead
* which might be better. See down for example. */
const Base *getBase() const { return this; }
virtual const Dervived *getDervived() const { return NULL; }
virtual bool operator==(const Base& other) const
{
printf("base calln");
}
};
class Dervived : public Base
{
public:
const Dervived *getDervived() const { return this; }
bool operator==(const Base& other) const
{
// case Base to Dervived here, either with dynamic_cast or C-style cast, but since you're very sure that other is Dervived, you can go for static_cast right away and no need to check for cast success.
printf("dervive calln");
}
};
int main()
{
Base *b = new Dervived();
Base *b2 = new Dervived();
if (*b == *b2); // calls Dervived::operator==
// ...
/* An alternative way of casting: */
const Dervived *d = dynamic_cast<Dervived *>(b);
if (d);
// cast successfull
/* Or use the members. */
d = b->getDervived();
if (d);
}
我更喜欢dynamic_cast的方式,但是,您所做的功能毫无用处,但我更喜欢使用这些函数,有时如下所示:
class Base
{
...
virtual bool isDervived() const { return false; }
};
class Dervived
{
...
bool isDervived() const { return true; }
};
注意在投射之前,你不需要比较,至少我会这样做。
等价关系不合适。std::multimap
和所有有序的关联容器都需要排序关系。因此,如果这是一个严格的要求,则应使用无序容器,即unordered_multimap。
无论哪种情况,您都需要提供一个函数对象,该对象接受两个Base*
参数a,b
并返回一个bool
,该根据您的定义表示是a<b
还是a==b
。
遗憾的是,C++一次只允许对一种类型的虚拟方法进行运行时查找,这里有两种。克服此限制的一种方法是双重调度方法。这样,通用的双参数函数对象
struct eq
{
bool operator()(const Base& a, const Base& b)
{
return a.isEqual(b);
}
};
最终将调用两个对象之一的正确isEqual
,例如,如果a
是这种类型的Der1
定义。现在在Der1
,一般定义是
bool isEqual(const Base& x) { return x.isEqual(*this); }
不幸的是,此时您必须在每个派生类中定义几个重载方法,例如isEqual(const Der1&)
、isEqual(const Der&)
等
class Der1
{
// ...
bool isEqual(const Base& x) { return x.isEqual(*this); }
bool isEqual(const Der1& x) { ... }
bool isEqual(const Der2& x) { ... }
bool isEqual(const Der3& x) { ... }
};
请注意,只有上面的前isEqual
是虚拟的,并覆盖Base
的方法。其余的是非虚拟重载,调用x.isEqual(*this)
将找到合适的重载,因为当*this
类型为Der2&
时,isEqual(const Der2& x)
将优先于isEqual(const Base& x)
(当然还有剩余的重载)。
这将顺利工作,而无需任何dynamic_cast
或恒定的运行时if
或switch
语句。但是,对于n
派生类,您需要在最坏的情况下n * (n+1)
isEqual
的定义(除非您利用层次结构中的常见模式并节省成本)。
此外,此方法违背了"添加新的派生类时,我不必更改或添加任何内容"的要求。再说一次,我不知道您如何期望不更改任何内容 - 您将如何比较新的派生类型?
很抱歉,我不知道任何真正优雅的解决方案。一般来说,如果可能的话,我更喜欢静态多态性,但在这里你需要一个单一类型的项目的容器,所以这不适用。
您可以使用多个调度:
以下内容可能会有所帮助(需要C++11): http://ideone.com/lTsc7M
#include <cstdint>
#include <array>
#include <iostream>
#include <tuple>
#include <type_traits>
/////////////////////////
#if 1 // multiple dispatch
// sequence of size_t // not in C++11
template <std::size_t ...> struct index_sequence {};
// Create index_sequence<0, >
template <std::size_t N, std::size_t ...Is>
struct make_index_sequence : make_index_sequence <N - 1, N - 1, Is... > {};
template <std::size_t ... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> {};
// Generic IVisitor
// Do: using MyIVisitor = IVisitorTs<Child1, Child2, ...>
template <typename ... Ts> class IVisitorTs;
template <typename T, typename ... Ts>
class IVisitorTs<T, Ts...> : public IVisitorTs<Ts...>
{
public:
using tuple_type = std::tuple<T, Ts...>;
using IVisitorTs<Ts...>::visit;
virtual void visit(const T& t) = 0;
};
template <typename T> class IVisitorTs<T>
{
public:
using tuple_type = std::tuple<T>;
virtual void visit(const T& t) = 0;
};
namespace detail {
// retrieve the index of T in Ts...
template <typename T, typename ... Ts> struct get_index;
template <typename T, typename ... Ts>
struct get_index<T, T, Ts...> : std::integral_constant<std::size_t, 0> {};
template <typename T, typename Tail, typename ... Ts>
struct get_index<T, Tail, Ts...> :
std::integral_constant < std::size_t, 1 + get_index<T, Ts...>::value > {};
// retrieve the index of T in Tuple<Ts...>
template <typename T, typename Tuple> struct get_index_in_tuple;
template <typename T, template <typename...> class C, typename ... Ts>
struct get_index_in_tuple<T, C<Ts...>> : get_index<T, Ts...> {};
// get element of a multiarray
template <std::size_t I>
struct multi_array_getter
{
template <typename T, std::size_t N>
static constexpr auto get(const T& a, const std::array<std::size_t, N>& index)
-> decltype(multi_array_getter<I - 1>::get(a[index[N - I]], index))
{
return multi_array_getter<I - 1>::get(a[index[N - I]], index);
}
};
template <>
struct multi_array_getter<0>
{
template <typename T, std::size_t N>
static constexpr auto get(const T& a, const std::array<std::size_t, N>& index)
-> decltype(a)
{
return a;
}
};
// Provide an implementation of visitor
// by forwarding to C implementation (which may be non virtual)
template <typename IVisitor, typename C, typename...Ts> struct IVisitorImpl;
template <typename IVisitor, typename C, typename T, typename...Ts>
struct IVisitorImpl<IVisitor, C, T, Ts...> : IVisitorImpl<IVisitor, C, Ts...>
{
virtual void visit(const T& t) override { C::visit(t); }
};
template <typename IVisitor, typename C, typename T>
struct IVisitorImpl<IVisitor, C, T> : IVisitor, C
{
virtual void visit(const T& t) override { C::visit(t); }
};
// helper to expand child type to IVisitorImpl
template <typename IVisitor, typename C>
struct IVisitorImplType;
template <typename ... Ts, typename C>
struct IVisitorImplType<IVisitorTs<Ts...>, C>
{
using type = IVisitorImpl<IVisitorTs<Ts...>, C, Ts...>;
};
// Create an multi array of pointer of function
// (with all combinaisons of overload).
template <typename Ret, typename F, typename Arg>
class GetAllOverload
{
private:
template <typename...Ts>
struct Functor
{
// function which will be in array.
static Ret call(F&f, const Arg& arg)
{
return call_helper(f, arg, make_index_sequence<sizeof...(Ts)>());
}
private:
// The final dispatched function
template <std::size_t ... Is>
static Ret call_helper(F&f, const Arg& arg, index_sequence<Is...>)
{
using RetTuple = std::tuple<Ts&...>;
// static cast is suffisant if arg is the abstract type
// when given arg is concrete type, reinterpret_cast is required.
// TODO: build a smaller table with only possible value to avoid that
return f(reinterpret_cast<typename std::tuple_element<Is, RetTuple>::type>(std::get<Is>(arg))...);
}
};
// helper class to create the multi array of function pointer
template <std::size_t N, typename Tuple, typename...Ts>
struct Builder;
template <typename...Ts, typename...Ts2>
struct Builder<1, std::tuple<Ts...>, Ts2...>
{
using RetType = std::array<Ret (*)(F&, const Arg&), sizeof...(Ts)>;
static constexpr RetType build()
{
return RetType{ &Functor<Ts2..., Ts>::call... };
}
};
template <std::size_t N, typename ...Ts, typename...Ts2>
struct Builder<N, std::tuple<Ts...>, Ts2...>
{
template <typename T>
using RecType = Builder<N - 1, std::tuple<Ts...>, Ts2..., T>;
using T0 = typename std::tuple_element<0, std::tuple<Ts...>>::type;
using RetType = std::array<decltype(RecType<T0>::build()), sizeof...(Ts)>;
static constexpr RetType build() {
return RetType{ RecType<Ts>::build()... };
}
};
public:
template <std::size_t N, typename VisitorTuple>
static constexpr auto get()
-> decltype(Builder<N, VisitorTuple>::build())
{
return Builder<N, VisitorTuple>::build();
}
};
template <typename Ret, typename IVisitor, typename F, std::size_t N>
class dispatcher
{
private:
std::array<std::size_t, N> index;
struct visitorCallImpl
{
template <typename T>
void visit(const T&) const
{
*index = get_index_in_tuple<T, IVisitor>::value;
}
void setIndexPtr(std::size_t& index) { this->index = &index; }
private:
std::size_t* index = nullptr;
};
template <std::size_t I, typename Tuple>
void set_index(const Tuple&t)
{
using VisitorType = typename IVisitorImplType<IVisitor, visitorCallImpl>::type;
VisitorType visitor;
visitor.setIndexPtr(index[I]);
std::get<I>(t).accept(visitor);
}
public:
template <typename Tuple, std::size_t ... Is>
Ret operator () (F&& f, const Tuple&t, index_sequence<Is...>)
{
const int dummy[] = {(set_index<Is>(t), 0)...};
static_cast<void>(dummy); // silent the warning unused varaible
constexpr auto a = GetAllOverload<Ret, F&&, Tuple>::
template get<sizeof...(Is), typename IVisitor::tuple_type>();
auto func = multi_array_getter<N>::get(a, index);
return (*func)(f, t);
}
};
} // namespace detail
template <typename Ret, typename Visitor, typename F, typename ... Ts>
Ret dispatch(F&& f, Ts&...args)
{
constexpr std::size_t size = sizeof...(Ts);
detail::dispatcher<Ret, Visitor, F&&, size> d;
return d(std::forward<F>(f), std::tie(args...), make_index_sequence<size>());
}
#endif // multiple dispatch
#if 1 // multiple dispatch usage
struct Square;
struct Rect;
struct Circle;
using IShapeVisitor = IVisitorTs<Square, Rect, Circle>;
struct IShape {
virtual ~IShape() = default;
virtual void accept(IShapeVisitor&) const = 0;
};
struct Rect : IShape {
virtual void accept(IShapeVisitor& v) const override { v.visit(*this); }
};
struct Square : Rect {
virtual void accept(IShapeVisitor& v) const override { v.visit(*this); }
};
struct Circle : IShape {
virtual void accept(IShapeVisitor& v) const override { v.visit(*this); }
};
class ShapePrinter : public IShapeVisitor
{
public:
void visit(const Rect& s) override { std::cout << "Rect"; }
void visit(const Square& s) override { std::cout << "Square"; }
void visit(const Circle& s) override { std::cout << "Circle"; }
};
struct IsEqual
{
bool operator() (IShape& s1, IShape& s2) const
{
ShapePrinter printer;
s1.accept(printer);
std::cout << " != ";
s2.accept(printer);
std::cout << std::endl;
return false;
}
template <typename S>
bool operator() (S& s1, S& s2) const
{
ShapePrinter printer;
s1.accept(printer);
std::cout << " == ";
s2.accept(printer);
std::cout << std::endl;
return true;
}
};
int main(int argc, char *argv[])
{
Rect rect;
Square sq;
Circle c;
IShape* shapes[] = { &rect, &sq, &c };
for (auto shape1 : shapes) {
for (auto shape2 : shapes) {
dispatch<bool, IShapeVisitor>(IsEqual(), *shape1, *shape2);
}
}
return 0;
}
#endif // multiple dispatch usage
- 继承层次结构并将元素添加到向量
- 如何在继承层次结构中调用具有默认参数的构造函数?
- 在C++继承层次结构时提取实现者
- 在继承层次结构中复制和移动
- 当中间类跳过实现时,在继承层次结构中执行哪种虚拟方法
- Sean Parent:对于继承层次结构中的多态类型,具有可变对象是极端的例外
- 检查类是否在继承层次结构中显式定义了成员类型
- 有关继承层次结构的问题,没有任何虚拟功能
- 在 c++ 中,通常比较继承层次结构中的对象
- 在继承层次结构中将方法定义为虚拟方法一次,以使多态性发挥作用
- 避免C++中继承层次结构中的重复代码
- dynamic_cast<>是否仅限于沿继承层次结构的直接强制转换?
- 如何为类的继承层次结构的获取者创建统一的接口
- 继承层次结构中级中的新虚函数
- 通过并行继承层次结构中的父级关联子项
- OO 正确性 - 继承层次结构 - 谁创建其他对象使用的对象
- 最佳继承层次结构,例如:Model3D -> ModelAnimation VS ModelAnimation or Model3D -> ModelAnimation
- 如何使用容器中对象的继承层次结构来维护开放/封闭原则
- 对于空类的单继承层次结构,是否可以保证大小相等
- 继承层次结构中接口与实现的分离(C++新手)