【问题标题】:Function for manipulating container of Base/Derived objects用于操作基础/派生对象容器的函数
【发布时间】:2009-04-22 00:55:00
【问题描述】:

考虑下面的数组算法:

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&lt;MyType, std::allocator&lt;MyType&gt; &gt;&amp; 类型的引用从 std::vector&lt;MySubType, std::allocator&lt;MySubType&gt; &gt; 类型的表达式的无效初始化

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

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

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

我会很高兴...

【问题讨论】:

    标签: c++ generics stl templates


    【解决方案1】:

    这可能会解决您的问题

    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
    }
    

    【讨论】:

    • 我喜欢这个,但不得不稍微改变一下。我在模板 中将 typename 放在 T 之前,我还必须将 typename 放在 std::vector 之前...不知道为什么...模板不太好
    • 之所以必须放“template”是因为模板参数有两种:类型和整数,你必须说T是哪一种。你必须放“ typename std::vector::iterator i" 因为编译器无法在没有帮助的情况下判断 "...::iterator" 是一个类型——没有 "typename" 它认为 "iterator" 是一个数据成员向量.
    • sorry yes 应该是"template ",我最近写了太多C#:P
    • 虽然这可行,但最好考虑使用 vector 代替,这将允许 vector 包含 MyType 或其任何派生类型的对象,但仅此而已。 (然后,您需要手动将 vector 复制到 vector 以传递给 foo()。)
    • 是的,我有类似的东西,但我发现自己在复制和粘贴循环结构......每次我复制和粘贴代码时,我都觉得哎呀——我应该把它放在一个通用函数中。 ..但是感谢 j_random_hacker
    【解决方案2】:

    为了扩展 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
    }
    

    【讨论】:

      【解决方案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。巨大的问题——我们永远无法确定我们的向量是否包含它们应该包含的类型!因此,编译不允许它。

      【讨论】:

      • 有一些数字确实支持这种协方差,只是不支持 C++
      • 但是我什么都不知道 ;-) 当然,像 python 这样的弱类型语言会允许你,但是有没有任何类型安全的语言可以让你?
      • Java 可以,但是你必须指定你是在做协变还是逆变。 Effective Java 引入了“PECS”概念:生产者扩展,消费者超。这意味着什么(如果您正在处理类型 T):如果传入容器用于从(用作生产者的容器)中取出项目,则将其指定为 "Collection extends T>";如果它用于将物品放入(用作消费者的容器),那么您将其指定为“Collection”。
      • 显然,如果一个函数需要获取一个集合来取出项目并将项目放回其中,那么类型将是不变的(“Collection”)。 :-P
      • .Net 4.0 中的 C# 也可以 - 您必须指定您是在执行“in”(协变)还是“out”(逆变)
      【解决方案4】:

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

      您的原始代码已损坏;或者如果你很幸运,它现在可能不会被破坏,但它非常脆弱,一旦有人做了比看MySubType 类更多的事情就会破坏。

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

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

      【讨论】:

      • 哦,是的,你的权利,对不起 - 我的意思是用一个指针数组来做这个
      • +1。 OP 已经纠正了原始基于指针的代码的问题,但它仍然保留在基于向量的代码中——作为第一步,您需要将向量分别声明为 vector 和 vector
      【解决方案5】:

      如果您想将多个不同的 MyType 派生类型的对象存储在单个向量中(我怀疑您会这样做,尽管对于这个特定示例来说这不是必需的),您需要使用 std::vector&lt;MyType*&gt; 来代替的std::vector&lt;MyType&gt;。这个建议类似于 Michael Burr 为您的原始指针代码提出的建议。

      这确实有一个不幸的副作用,即您不能将std::vector&lt;MySubType*&gt; 隐式转换为std::vector&lt;MyType*&gt; 以调用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() 从一开始就使用std::vector&lt;MyType*&gt;

      【讨论】:

      • 感谢 j_random_hacker...这是一个不错的解决方案,但我更喜欢模板函数...我也没有提到我实际上并没有在 std::vector 上执行此操作,但是在 std::vector 之类的东西上。特殊容器中的项目不能设计为指针(它们都必须至少是特定类型,而不是指针)。我没有在我的原始帖子中解释这一点,因为它的打字太多无法解释。 :(
      • 没问题 :) 如果元素被存储为对象而不是指向它们的指针,那么 every 函数你想对各种类型进行操作(例如 MyType 和MySubType) 需要制作成函数模板。
      【解决方案6】:

      C++ 不支持模板类型的协变,这与 C# 目前所做的一样,原因大致相同。如果您想这样做,最好将您的向量模板化为一个通用接口类型(指向该类型的指针),并让函数 foo 接受该类型的向量。

      【讨论】:

      • 一直?真的吗?模板参数不相同的模板类之间可以赋值吗?
      • 需要成为基类指针的向量才能工作。由于不建议使用 STL 和原始指针,因此建议使用智能指针。
      【解决方案7】:

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

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

      【讨论】:

        【解决方案8】:

        使用 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 ); 
        }
        

        【讨论】:

        • 提升智能指针,指向基类的指针解决了问题。
        【解决方案9】:

        如果我知道这些 foobars 背后的真正代码是什么,那么也许我会知道得更好,但是 STL 中是否已经有解决您的问题的方法?

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

        只需将“do stuff on *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());
        

        【讨论】:

        • 我喜欢这个,但我认为kuoson的模板解决方案更优雅。谢谢威廉泰尔。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-26
        • 1970-01-01
        • 1970-01-01
        • 2019-08-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多