用于操作Base / Derived对象的容器的函数

时间:2009-04-22 00:55:00

标签: c++ generics stl templates

考虑使用数组的以下算法:

class MyType;
{
    // some stuff
}

class MySubType:MyType
{
    // some stuff
}

void foo(MyType** arr, int len)
{
    for (int i = 0;i<len;i++)
        // do something on arr[i]->
}

void bar()
{
    MySubType* arr[10];
    // initialize all MySubType*'s in arr
    foo(&arr, 10);
}

这里没什么特别的。我的问题是 - 如何使用模板执行此操作?

void foo(std::vector<MyType>& s)
{
    std::vector<MyType>::iterator i;
    for (i = s.begin(); i != s.end(); i++)
        // do stuff on *i
}

所以,在吧,我不能这样做:

void bar()
{
    std::vector<MySubType> s;
    foo(s);  // compiler error
}

错误:从类型std::vector<MyType, std::allocator<MyType> >&

的表达式初始化std::vector<MySubType, std::allocator<MySubType> >类型的引用无效

有没有办法做这样的事情?

基本上,如果有办法做到这一点:

std::vector<MySubType> s;
std::vector<MyType>& t = s;

我很高兴......

9 个答案:

答案 0 :(得分:8)

这可能会解决您的问题

template <typename T>
void foo(std::vector<T>& s)
{
    typename std::vector<T>::iterator i;
    for (i = s.begin(); i != s.end(); i++)
        // do stuff on *i
}

答案 1 :(得分:6)

要扩展kuoson's answer,惯用的C ++风格是将迭代器传递给函数而不是容器。

template<typename Iterator>
void foo(const Iterator & begin, const Iterator & end)
{
    Iterator i;
    for (i = begin;  i != end;  ++i)
        // do stuff on *i
}

答案 2 :(得分:3)

这就是问题所在 - 如果s和t指向同一个对象,那么阻止你将MyOtherSubType(与MySubType无关)放入t中的是什么?这将使s包含不是MySubType的对象。我不知道任何类型安全的编程语言可以让你这样做。如果允许的话,想象一下我们遇到的问题:

//MySubType inherits from MyType
//MyOtherSubType also inherits from MyType

std::vector<MySubType> s;
std::vector<MyType>& t = s;

MyOtherSubType o;
t.push_back(o);

由于t和s在不同名称下是完全相同的对象,因此s [0]处的元素不是MySubType。巨大的问题 - 我们永远无法确定我们的向量是否包含它们应该具有的类型!因此,编译不允许它。

答案 3 :(得分:2)

既然你说“这里没什么特别的东西”,我不确定你是否意识到这一点:

您的原始代码已损坏;或者,如果你很幸运,它现在可能不会被打破,但是它很脆弱,只要有人做了一些事情而不是看MySubType课程就会打破。

问题在于您将MyType*传递给foo(),但它确实指向MySubType的数组。如果MySubType对象恰好大于MyType对象(如果你已经向派生类添加了任何东西,那很可能),那么在foo()函数中完成指针算术将是不正确的。

这是派生类对象数组的严重和经典陷阱之一。

答案 4 :(得分:1)

如果你想在一个向量中存储多个不同MyType派生类型的对象(我怀疑你这样做,虽然这个特定的例子没有必要),你需要使用{{ 1}}而不是std::vector<MyType*>。这个建议类似于Michael Burr提出的原始指针代码。

这确实有一个令人遗憾的副作用,你不能隐含地将std::vector<MyType>转换为std::vector<MySubType*>来调用std::vector<MyType*>。但转换代码并不太繁琐:

foo()

或者,让void foo(std::vector<MyType*>& s) { ... } void bar() { std::vector<MySubType*> s; // Populate s ... std::vector<MyType*> u(s.begin(), s.end()); // Convert foo(u); } 从头开始使用bar()

答案 5 :(得分:0)

C ++不支持模板类型的协方差,不仅仅是C#目前所做的,出于同样的原因。如果你想这样做,最好让你的矢量在一个通用的接口类型(指向那种类型的指针)上进行模板处理,并使函数foo接受这种类型的矢量。

答案 6 :(得分:0)

不幸的是,没有。不同的模板特化被认为是不同的类;您无法在std::vector<MyType>std::vector<MySubType>之间进行转换。

你可以将foo()的“do stuff”公共位拆分成一个单独的函数,并在每个向量上有一个单独的循环(或者可能使用std::foreach)。

答案 7 :(得分:0)

使用boost(用于智能指针):

foo( std::vector<boost::shared_ptr<MyType> >& v )
{
   std::for_each( v.begin(),
                  v.end(),
                  do_something );
}

bar()
{
    std::vector<boost::shared_ptr<MyType> > s;
    // s.push_back( boost::shared_ptr<MyType> ( new MySubType() ) );
    foo( s ); 
}

答案 8 :(得分:0)

如果我知道那些foobars背后的实际代码是什么,那么也许我会更清楚,但是在STL中你的问题是不是已经解决了?

for_each(s.begin(), s.end(), DoStuffOnI());

只需将您的“在* i上执行代码”代码放在函数或函数中:

struct DoStuffOnI : public std::unary_function<MyType&,void> {
    void operator()(MyType& obj) {
        // do stuff on *i
    }
};

如果你对发送两个参数而不是一个参数感到困扰,那么好吧,也许你可以这样做:

template<typename In>
struct input_sequence_range : public std::pair<In,In> {
    input_sequence_range(In first, In last) : std::pair<In,In>(first, last)
    {
    }
};

template<typename C>
input_sequence_range<typename C::iterator> iseq(C& c)
{
    return input_sequence_range<typename C::iterator>(c.begin(), c.end());
}

template<typename In, typename Pred>
void for_each(input_sequence_range<In> r, Pred p) {
    std::for_each(r.first, r.second, p);
}

然后像这样调用for_each:

for_each(iseq(s), DoStuffOnI());