【问题标题】:c++ problem with polymorphism and vectors of pointers多态性和指针向量的c ++问题
【发布时间】:2009-11-16 11:52:37
【问题描述】:

考虑以下示例代码:

class Foo
{
};

class Bar : public Foo
{
};

class FooCollection
{
protected:
    vector<shared_ptr<Foo> > d_foos;
};

class BarCollection : public FooCollection
{
public:
    vector<shared_ptr<Bar> > &getBars()
    {
        // return d_foos won't do here...
    }
};

我在当前的项目中遇到了这样的问题。客户端代码使用BarCollection,它将指向Bars 的指针存储在d_foos 中,FooCollection 中声明。我现在想将指向 Bars 的指针集合公开给客户端代码。我可以让客户端代码访问指向Foos 的指针向量,并将它们转换为客户端代码中指向Bars 的指针,但这感觉不对,因为客户端不必知道Foo的存在。

我还可以定义一个get() 成员,它从d_foos 检索对象并强制转换它们,但这感觉很笨拙。最好将 d_foos 作为vector&lt;shared_ptr&lt;Bar&gt; &gt; &amp; 返回,但我似乎无法做到这一点。

也可能是我的设计完全错误。不过,这似乎是最自然的解决方案,因为 BarFoo 的特化,BarCollectionFooCollection 的特化,它们共享功能。

您能否建议在BarCollection 中实现getBars 或更好的设计替代方案的好解决方案?

编辑:

事实证明我的设计确实很糟糕。 BarCollection 不是 FooCollection,尽管它需要 FooCollection 的所有功能。我目前基于以下答案的解决方案——更简洁——现在是:

class Foo
{
};

class Bar : public Foo
{
};

template<class T>
class Collection
{
    vector<shared_ptr<T> > d_items;
};

typedef Collection<Foo> FooCollection;

class BarCollection : public Collection<Bar>
{
    // Additional stuff here.
};

感谢所有优秀的建议和示例!

【问题讨论】:

  • 我已经发布了一个解决方案……主要是为了完整性,因为它涉及reinterpret_cast。我强烈敦促您考虑重构替代方案。特别是,您将继承引入的IS-A 关系与代码重用混为一谈。此外,您不想在那里有getBars。暴露你的私处被认为是暴露狂,会受到法律的惩罚。
  • @Matthieu:我已经为这个问题添加了我重构的当前解决方案。谢谢!

标签: c++ vector polymorphism shared-ptr


【解决方案1】:

我建议从您的容器类中公开迭代器,而不是从成员容器中公开。这样,容器类型是什么就无关紧要了。

【讨论】:

  • 你大错特错了。取消引用std::vector&lt;Foo&gt;::iterator 仍然会给出Foo&amp;,这不是您想要的BarCollection
【解决方案2】:

问题是您试图以一种行不通的方式混合和匹配两种不同的、几乎独立的多态性。模板的编译时类型安全多态性不允许您用基类型替换派生类型。 C++ 的模板系统在

之间没有关联
class<Foo>

class<Bar>

一个建议可能是创建一个 Foo 派生适配器,该适配器将向下转换为正确的类:

 template <class derived, class base>
 class DowncastContainerAdapter
 {
 private:
     std::vector< boost::shared_ptr<base> >::iterator curr;
     std::vector< boost::shared_ptr<base> >::const_iterator end;
 public:
     DowncastContainerAdapter(/*setup curr & end iterators*/)
     {
         // assert derived actually is derived from base
     }

     boost::shared_ptr<derived> GetNext()
     {
         // increment iterator
         ++curr;
         return dynamic_cast<base>(*curr);
     }

     bool IsEnd()
     {
         return (curr == end);
     }
 };

注意这个类会和迭代器有同样的问题,对向量的操作可能会使这个类失效。

另一个想法

您可能没有意识到这一点,但只返回一个 Foo 的向量可能完全没问题。 Bar 的用户必须完全了解 Foo,因为通过包含 Bar.h,他们必须通过 Bar.h 获取 Foo.h。原因是 Bar 要从 Foo 继承,它必须通过 Foo.h 完全了解该类。如果可以使 Foo(或 Foo 的超类)成为接口类并传递指向该接口类的指针向量,我建议不要使用上述解决方案。这是一个非常常见的模式,不会引起我想出的这个不稳定的解决方案的眉毛 :)。然后你可能有你的理由。祝你好运。

【讨论】:

  • @另一个想法:好吧,Bar 可能会引入您真正想要拥有的功能,而不是自己投射。
【解决方案3】:
template<class T>
class MyContainer {
  vector<shared_ptr<T> > d_foos;
public:
  vector<shared_ptr<T> > & getVector();
};

class FooCollection : public MyContainer<Foo> {
};

class BarCollection : public MyContainer<Bar> {
};

【讨论】:

    【解决方案4】:

    问题是,你为什么要这样做?如果您给用户一个指向 Bar 的指针集合,您会假设其中只有 Bars,因此在内部将指针存储在指向 Foo 的集合中是没有意义的。如果在指向 Foo 的指针集合中存储不同的 Foo 子类型,则不能将其作为指向 Bar 的指针集合返回,因为其中并非所有对象都是 Bars。 在第一种情况下,(你知道你只有酒吧)你应该使用上面建议的模板化方法。 否则,你必须重新考虑,你真正想要什么。

    【讨论】:

      【解决方案5】:

      你不能用 Foo / Bar 上模板化的 Collection 替换它吗?像这样

      class Collection<T> {
      protected:
          vector<shared_ptr<T> > d_foos;
      };
      
      typedef Collection<Foo> FooCollection;
      typedef Collection<Bar> BarCollection;
      

      【讨论】:

      • 如果您想提供多态方法,可能不会。
      【解决方案6】:

      您是否特别需要从FooCollection 派生BarCollection?因为通常BarCollection不是FooCollection,通常很多可以用FooCollection 完成的事情不应该用BarCollection 完成。例如:

      BarCollection *bc = new BarCollection();
      FooCollection *fc = bc; // They are derived from each other to be able to do this
      fc->addFoo(Foo());      // Of course we can add a Foo to a FooCollection
      

      现在我们添加了一个Foo 对象到应该是BarCollection 的对象。如果BarCollection 试图访问这个新添加的元素并期望它是Bar,那么各种丑陋的事情就会发生。

      因此,通常您希望避免这种情况,并且不要让您的集合类相互派生。有关此主题的更多答案,另请参阅questions 关于派生类型的casting containers...

      【讨论】:

      • 很好的例子说明了我最初的设计为什么不好。 +1!
      【解决方案7】:

      首先说一下shared_ptr。你知道吗:boost::detail::dynamic_cast_tag

      shared_ptr<Foo> fooPtr(new Bar());
      shared_ptr<Bar> barPtr(fooPtr, boost::detail::dynamic_cast_tag());
      

      这是一个非常方便的方法。在封面下,它只是执行dynamic_cast,没有什么花哨的,而是一个更简单的符号。合约与经典合约相同:如果指向的对象实际上不是Bar(或派生自它),那么您将获得一个空指针。

      回到你的问题:错误的代码。

      BarCollection 不是FooCollection,因此您遇到了麻烦,因为您可以在Bar 的指针向量中引入其他元素。

      不过我不会就此展开,因为这超出了手头的问题,我认为我们(作为那些试图回答的人)应该克制自己。

      您不能传递引用,但可以传递View

      基本上,View 是一个新对象,它充当旧对象的Proxy。使用示例中的Boost.Iterators 相对容易。

      class VectorView
      {
        typedef std::vector< std::shared_ptr<Foo> > base_type;
      
      public:
        typedef Bar value_type;
        // all the cluttering
      
        class iterator: boost::iterator::iterator_adaptor<
          iterator,
          typename base_type::iterator,
          std::shared_ptr<Bar>
        >
        {
          typename iterator_adaptor::reference dereference() const
          {
            // If you have a heart weakness, you'd better stop here...
            return reinterpret_cast< std::shared_ptr<Bar> >(this->base_reference());
          }
        };
      
        // idem for const_iterator
      
        // On to the method forwarding
        iterator begin() { return iterator(m_reference.begin()); }
      
      private:
        base_type& m_reference;
      }; // class VectorView
      

      这里真正的问题当然是reference 位。获取NEW shared_ptr 对象很容易,并允许根据需要执行dynamic_cast。将reference 转换为ORIGINAL shared_ptr 但被解释为所需的类型……这真的不是我喜欢在代码中看到的。

      注意
      可能有比使用 Boost.Fusion transform_view 类做得更好的方法,但我想不通。

      特别是,使用transform_view 我可以获得shared_ptr&lt;Bar&gt;,但是当我取消引用我的迭代器时我无法获得shared_ptr&lt;Bar&gt;&amp;,这很烦人,因为唯一使用返回对底层vector 的引用(而不是const_reference)实际上是修改vector的结构及其包含的对象。

      注2
      请考虑重构。那里有很好的建议。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-10
        • 2021-09-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-06-28
        相关资源
        最近更新 更多