具有非成员函数的std :: vector中的多态性

时间:2015-12-05 19:32:58

标签: c++ c++11 vector polymorphism

我已经尝试发布这个问题,但是每个人都抱怨,我的问题很难理解并要求我提供MCVE,所以我决定再次提出这个问题提供一个例子:

http://pastebin.com/SvidcrUi

我有一个问题,我遇到过。我在使用Root的C ++ 11中工作,我有一个std :: vector,它包含TH1D *和TH2D *类型的变量(直方图)。我不被允许触及TH1D或TH2D定义,但我根据变量的类型做了不同的事情。

每当我想调用一个可以处理这两种情况的重载函数时,我的编译器大声说“调用重载函数是模糊的”......我知道它是,但我需要帮助找出更好的设计..我应该怎么做?

我在代码中的问题:

void save_histograms_from_vector( const std :: vector<TH1*>& histogram_vector_p )
{
    for( auto& histogram: histogram_vector_p )
    {
        save_histogram_as_canvas( histogram ); //overloaded function
    }
}
(...)
template<typename TH_type_1, typename TH_type_2, typename TH_type_3, typename TH_type_4> 
void save_as_two_by_two_canvas( TH_type_1 histogram_1_p, TH_type_2 histogram_2_p, TH_type_3 histogram_3_p, TH_type_4 histogram_4_p, TFile* output_file_p, const std :: string& directory_name_p, const std :: string& canvas_name_p, const std :: string& canvas_title_p )
{
     (...)
     const std :: vector<TH1*> histograms = { histogram_1_p, histogram_2_p, histogram_3_p, histogram_4_p };
     save_histograms_from_vector( histograms );
     // This would have worked if I called the function Write() for each of the histograms )
}

因此,对于该示例,我的目标是编写可以实现message_of_instances()函数应该执行的操作的函数。 这个例子现在不编译,但它唯一有问题的是,它不能推断出std :: vector中元素的类型。如果我要调用元素的成员函数,就像简单的write()一样。

我的问题是:针对这些问题是否有解决方法?

感谢您提出的所有建设性意见!!

2 个答案:

答案 0 :(得分:1)

您可以使用boost :: variant来包装特定类型(boost变量然后跟踪,它是从哪个类型创建的)。然后检查每个值实际上是哪个类型(boost :: variant :: which)或者更好的是变体访问者对特定类型应用操作。

或者你确实可以自己滚动更简单的东西(基本上是一个包装器,为每种可能的类型提供构造函数,并跟踪构造它的类型,这就是boost :: variant在原理中的作用)。或者使用union(boost :: variant是C ++替换联合)。

编辑:这是一个如何在不改变类的情况下完成的示例。基本上引入了一个包装器,它将存储类型擦除的实现,跟踪实际类型(只是快速写入,可能需要一些抛光):

class BaseWrapper
{
public:
    template<typename TH_TYPE>
    BaseWrapper(TH_TYPE *x)
        : impl(createImpl(x))
    {}
    BaseWrapper()
        : impl(nullptr)
    {}
    BaseWrapper(const BaseWrapper &other)
        : impl(cloneImpl(other.impl))
    {}
    BaseWrapper & operator =(const BaseWrapper &other)
    {
        if (this != &other)
        {
            ImplBase *newImpl = cloneImpl(other.impl);
            delete impl;
            impl = newImpl;
        }
        return *this;
    }
    ~BaseWrapper()
    {
        delete impl;
    }

    void doStuff() const
    {
        if (impl)
            impl->doStuff();
    }
private:

    class ImplBase {
    public:
        ImplBase(Base *x)
            : ptr(x)
        {}
        virtual ImplBase *clone() const = 0;
        virtual void doStuff() const = 0;
    protected:
        Base *ptr;
    };

    template<typename TH_TYPE>
    class Impl: public ImplBase {
    public:
        Impl(Base *x)
            : ImplBase(x)
        {}
        ImplBase *clone() const
        {
            return new Impl<TH_TYPE>(*this);
        }
        void doStuff() const
        {
            if (ptr)
                write_and_do_other_stuffs( static_cast<TH_TYPE *>(ptr) );
        }
    };

    template<typename TH_TYPE>
    static ImplBase *createImpl(TH_TYPE *x)
    {
        return new Impl<TH_TYPE>(x);
    }

    static ImplBase * cloneImpl(ImplBase *impl)
    {
        return impl ? impl->clone() : impl;
    }

    ImplBase *impl;
};

然后,使用std::vector<BaseWrapper>代替std::vector<Base *>并调用doStuff,它将调用转发到BaseWrapper :: Impl,提供具有正确类型的真实调用。可以扩展为Base *提供RAII,通过仿函数等调用不同的方法。

编辑#2:使用boost :: variant它看起来像这样:

#include <boost/variant.hpp>

typedef boost::variant<Derived_one *, Derived_two *> BaseVariant_t;

struct MyVisitor: boost::static_visitor<>
{
    template<typename TH_TYPE>
    void operator()(TH_TYPE * ptr) const
    {
        write_and_do_other_stuffs( ptr );
    }
};

void message_of_instances( const std :: vector<BaseVariant_t>& instances_p )
{
    for( auto it = instances_p.begin(); it != instances_p.end();++it )
    {
        boost::apply_visitor(MyVisitor(), *it);
    }
}

正如你所看到的,更优雅,但它有一个限制,你需要预先知道所有可能的类型(boost :: variant需要知道所有这些类型,它们必须作为模板参数提供) 。上面的包装器解决方案没有这个限制,但需要付出代价 - 有额外的内存分配(boost :: variant不需要任何额外的内存分配)和虚方法调用(boost :: variant static_visitor使用模板机制,所以电话是直接的。)

请注意,访问者可以提供全局模板化访问方法(如示例中所示),也可以为每种类型提供单独的operator()。或者甚至将两者结合起来(对于某些类型具有单独的运算符,对于其余类型具有模板化解决方案)。

事实上,包装器解决方案也可以扩展为使用访问者(而不是必须提供额外的方法来调用每个不同的方法)。

答案 1 :(得分:1)

为了正确利用多态性,您需要在基类中声明所有函数,然后可以在派生类中重写。

如果这不起作用,您可以执行以下操作之一:

  • 使用dynamic_cast将基类向下转换为派生类。

  • 使用Visitor模式。您可以在此网站和其他网站上查找其定义和使用。