多态性和指针向量的c ++问题

时间:2009-11-16 11:52:38

标签: c++ vector polymorphism shared-ptr

请考虑以下示例代码:

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,其存储Barsd_foos的{​​{1}}指针,该FooCollectionFoo中声明。我现在想要向客户端代码公开指向Bars的指针集合。我可以让客户端代码访问指向Bar的指针向量并将其转换为客户端代码中指向Foo的指针,但这感觉不对,因为客户端不必知道关于get()的存在。

我还可以定义一个d_foos成员从vector<shared_ptr<Bar> > &中检索对象并对其进行转换,但这感觉非常笨拙。我希望将d_foos作为Bar返回,但我似乎无法做到这一点。

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

您能否建议在BarCollection或更好的设计方案中实施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. }; 的好解决方案?

修改

原来我的设计确实很糟糕。尽管要求所有的FooCollection功能,但BarCollection不是FooCollection。我现在的解决方案基于以下答案 - 现在更清洁 - 现在:

{{1}}

感谢所有出色的建议和例子!

7 个答案:

答案 0 :(得分:3)

问题是你试图以一种不起作用的方式混合和匹配两种不同的,非常独立的多态性。模板的编译时类型安全多态性将不允许您将基类型替换为派生类型。 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的超类)成为接口类并传递指向该接口类的指针向量。这是一个非常常见的模式,并不会引起我想出的这个难以理解的解决方案的眉毛:)。然后你可能有你的理由。祝你好运。

答案 1 :(得分:3)

我建议从容器类中公开迭代器,而不是成员容器。这样,容器类型是什么并不重要。

答案 2 :(得分:2)

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> {
};

答案 3 :(得分:2)

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

答案 4 :(得分:1)

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

class Collection<T> {
protected:
    vector<shared_ptr<T> > d_foos;
};

typedef Collection<Foo> FooCollection;
typedef Collection<Bar> BarCollection;

答案 5 :(得分:1)

您是否特别需要BarCollection来自FooCollection?由于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以获取有关此主题的更多答案...

答案 6 :(得分:1)

首先,我们来谈谈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(或从它派生),那么你得到一个空指针。

回到你的问题:BAD CODE。

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。获取referenceORIGINAL shared_ptr但被解释为所需类型...实际上并不是我希望在代码中看到的。

注意
可能有一种方法比使用Boost.Fusion transform_view类更好,但我无法理解。

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

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