我已经尝试发布这个问题,但是每个人都提示我的问题很难理解,并要求我提供一个 MCVE,所以我决定再次提出这个问题并提供一个例子:
我遇到了一个问题。我在 C++11 中与 Root 一起工作,我有一个 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() 一样有效。
我的问题是:是否有解决此类问题的方法?
谢谢大家的建设性意见!!
最佳答案
您可以使用 boost::variant 来包装特定类型(然后 boost 变体会跟踪它是从哪种类型创建的)。然后检查每个值的实际类型 (boost::variant::which) 或者更好的是变体访问者对特定类型应用操作。
或者你确实可以自己推出一些更简单的类似东西(基本上是一个包装器,为每种可能的类型提供构造函数并跟踪它是从哪个类型构造的,这就是 boost::variant 在原理中所做的)。或者使用 union(boost::variant 是 union 的 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()。或者甚至将两者结合起来(对某些类型使用单独的运算符,对其余类型使用模板化解决方案)。
事实上,包装器解决方案也可以扩展为使用访问者(而不是必须提供额外的方法来调用每个不同的方法)。
关于c++ - 具有非成员函数的 std::vector 中的多态性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34109903/