3

考虑以下示例代码:

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其中声明的指针FooCollection。我现在想将指向 Bars 的指针集合公开给客户端代码。我可以让客户端代码访问指向Foos 的指针向量并将它们转换为Bar客户端代码中指向 s 的指针,但这感觉不对,因为客户端不必知道Foo' 的存在。

我还可以定义一个get()成员来从中检索对象d_foos并对其进行强制转换,但这感觉很笨拙。最好,我只想将 d_foos 作为 a 返回vector<shared_ptr<Bar> > &,但我似乎无法做到这一点。

也可能是我的设计完全错误。不过,这似乎是最自然的解决方案,因为Bar它是专业化的,Foo并且BarCollection是专业化的,FooCollection并且它们共享功能。

您能否提出很好的解决方案来实施getBarsBarCollection更好的设计替代方案?

编辑:

原来我的设计确实很糟糕。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.
};

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

4

7 回答 7

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 的超类)成为接口类并传递指向该接口类的指针向量,我建议不要使用上述解决方案。这是一种非常常见的模式,不会像我想出的这种不稳定的解决方案那样引起人们的注意:)。然后你可能有你的理由。不管怎样,祝你好运。

于 2009-11-16T12:05:25.187 回答
3

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

于 2009-11-16T12:08:54.017 回答
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> {
};
于 2009-11-16T12:01:15.303 回答
2

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

于 2009-11-16T12:21:07.850 回答
1

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

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

typedef Collection<Foo> FooCollection;
typedef Collection<Bar> BarCollection;
于 2009-11-16T11:57:16.137 回答
1

您是否有特殊需要BarCollection衍生自FooCollection? 因为一般 aBarCollection 不是a FooCollection,通常很多可以用 a 完成的事情FooCollection不应该用 a 完成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试图访问这个新添加的元素并期望它是 a Bar,就会发生各种丑陋的事情。

所以通常你想避免这种情况并且不要让你的集合类相互派生。有关此主题的更多答案,另请参阅有关转换派生类型容器的问题...

于 2009-11-16T12:31:00.563 回答
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, 没什么特别的,但一个更简单的符号。合约与经典合约相同:如果指向的对象实际上不是 a Bar(或从它派生),那么您将获得一个空指针。

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

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

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

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

基本上,aView是一个新对象,它充当Proxy旧对象的 a。使用示例中的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_viewI can get shared_ptr<Bar>but I can't get a shared_ptr<Bar>&when I dereference my iterator,这很烦人,因为返回对底层vector(而不是 a const_reference)的引用的唯一用途是实际修改 thevector和 the的结构它包含的对象。

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

于 2009-11-16T13:01:49.390 回答