问题
I am planning to write a multimap like this
std::multimap <key, base_ptr> mymap;
And I would like to be able to store pointers of many derived classes (say Der1
, Der2
) which derive from the base.
Now when I am trying to insert an object into the map, I first do a lookup on the key and then I need to compare if the object is EQUIVALENT (does not have to be the same object hence not doing a pointer comparison) to the one at that location. So for this lets say I override the == operator
or write some kind of a compare function. Now I would like to write the code for this in such a manner that when new derived classes are added, I dont have to change or add anything.
So I am thinking there has to be a generic way of writing this. But not able to think of one.
I was thinking of something like the following
class Base
{
virtual Base * get() { return this; }
virtual bool isEqual(const Base& toObj) {
....
}
}
class Der1
{
Der1 * get() { return this; }
bool isEqual(const Der1& toObj) {
....
}
}
But this does not seem to work either. because when I do:
Base* bp1;
Base* bp2;
bp1->get()->isEqual(*(bp2->get()))
I see that the call to get()
does end up in the get()
of the derived class as I expect, but then the compiler treats the returned value as Base*
. This is most likely because its a run-time polymorphism. But I find it hard to believe that there wont be an elegant and obvious way to do this.
Can somebody advise.
回答1:
You might try something like this:
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);
}
回答2:
It's quite simple, read the comments on this code:
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);
}
I prefer the dynamic_cast way, however, the functions you made are quite useless, but i prefer to use these kind of functions sometimes like so:
class Base
{
...
virtual bool isDervived() const { return false; }
};
class Dervived
{
...
bool isDervived() const { return true; }
};
Note You shouldn't need to compare before casting, at least this is what I would do.
回答3:
An equivalence relation is not appropriate. std::multimap
and all ordered associative containers need an ordering relation. So if this a strict requirement, you should use an unordered container, that is, unordered_multimap.
In either case, you need to provide a function object that takes two Base*
arguments a,b
and returns a bool
that says whether a<b
or a==b
according to your definition.
Unfortunately, C++ permits run-time lookup for virtual methods only for one type at a time, and here you have two. One way over this limitation is the double dispatch method. This way, the generic two-argument function object
struct eq
{
bool operator()(const Base& a, const Base& b)
{
return a.isEqual(b);
}
};
will end up calling the right isEqual
of one of the two objects, e.g. the definition of Der1
if a
is of this type. Now in Der1
, the general definition would be
bool isEqual(const Base& x) { return x.isEqual(*this); }
Unfortunately at this point you would have to define several overloaded methods in each derived class e.g. isEqual(const Der1&)
, isEqual(const Der&)
etc.
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) { ... }
};
Note that only the first isEqual
above is virtual and overrides Base
's method. The rest are non-virtual overloads and the call x.isEqual(*this)
will find the appropriate one because when *this
is of type Der2&
then isEqual(const Der2& x)
will be preferred over isEqual(const Base& x)
(and the remaining overloads of course).
This will work smoothly without the need for any dynamic_cast
or constly run-time if
or switch
statements. However, for n
derived classes you need n * (n+1)
definitions of isEqual
in the worst case (except if you exploit common patterns in the hierarchy and make savings).
Also, this approach defeats your requirement that "when new derived classes are added, I dont have to change or add anything". Then again, I don't know how you'd expect not to change anything - how would you compare a new derived type?
In am sorry I don't know any really more elegant solution. In general I prefer static polymorphism when possible but here you need a container of items of a single type so this does not apply.
回答4:
You may use Multiple dispatch:
Following may help (require 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
来源:https://stackoverflow.com/questions/21529062/generically-comparing-objects-in-an-inheritance-hierarchy-in-c