我打算像这样编写一个多图
std::multimap <key, base_ptr> mymap;
我希望能够存储从基础派生的许多派生类(例如Der1
,Der2
)的指针。
现在当我试图将一个对象插入到地图中时,我首先对该键进行查找,然后我需要比较该对象是否为EQUIVALENT(不必是同一个对象因此不进行指针比较)到那个地方的人。因此,我可以覆盖== 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*
。这很可能是因为它是一个运行时多态性。但我发现很难相信这不会是一种优雅而明显的方法。
有人可以提供建议。
答案 0 :(得分:0)
您可以尝试这样的事情:
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);
}
答案 1 :(得分:0)
等价关系不合适。 std::multimap
和所有有序的关联容器都需要有序关系。因此,如果这是一个严格的要求,您应该使用无序容器,即unordered_multimap。
在任何一种情况下,您都需要提供一个函数对象,该对象需要两个Base*
参数a,b
并返回一个bool
,表示a<b
或a==b
根据你的定义。
不幸的是,C ++允许一次只为一种类型的虚拟方法运行时查找,这里有两个。超越此限制的一种方法是double dispatch方法。这样,通用的双参数函数对象
struct eq
{
bool operator()(const Base& a, const Base& b)
{
return a.isEqual(b);
}
};
最终会调用两个对象之一的右isEqual
,例如如果Der1
属于此类型,则a
的定义。现在在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
的定义(除非您在层次结构中利用常见模式并节省成本)。
此外,这种方法违背了“当添加新的派生类时,我不必更改或添加任何内容”的要求。然后,我不知道你怎么期望不改变任何东西 - 你会如何比较一个新的衍生类型?
对不起,我不知道任何更优雅的解决方案。一般来说,我更喜欢静态多态,但是在这里你需要一个单一类型的容器,所以这不适用。
答案 2 :(得分:0)
这很简单,请阅读有关此代码的评论:
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 call\n");
}
};
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 call\n");
}
};
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; }
};
注意在投射之前你不需要比较,至少这是我要做的。
答案 3 :(得分:0)
您可以使用Multiple dispatch:
以下可能有所帮助(需要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