通常在c ++中比较继承层次结构中的对象

时间:2014-02-03 13:47:16

标签: c++ generics inheritance polymorphism generic-programming

我打算像这样编写一个多图

std::multimap <key, base_ptr> mymap;

我希望能够存储从基础派生的许多派生类(例如Der1Der2)的指针。

现在当我试图将一个对象插入到地图中时,我首先对该键进行查找,然后我需要比较该对象是否为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*。这很可能是因为它是一个运行时多态性。但我发现很难相信这不会是一种优雅而明显的方法。

有人可以提供建议。

4 个答案:

答案 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<ba==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或常量运行时ifswitch语句。但是,对于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