2009-11-16 93 views
3

的载体请看下面的例子代码:C++问题多态性和指针

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的指针集合暴露给客户端代码。我可以让客户端代码访问指向Foo s的指针矢量,并在客户端代码中将这些指针指向Bar s,但由于客户端不需要知道Foo的存在,因此感觉错误。

我也可以定义一个get()成员,它从d_foos中检索对象并投射它们,但是这种感觉很笨拙。最好,我想只是返回d_foos作为vector<shared_ptr<Bar> > &,但我似乎无法做到这一点。

也可能是我的设计显然是错的。尽管这似乎是最自然的解决方案,因为BarFoo的专业化版本,而BarCollectionFooCollection的专业版本,它们共享功能。

你能提出一个很好的解决方案来实施getBarsBarCollection或更好的设计方案吗?

编辑:

原来我的设计是坏确实如此。尽管需要所有FooCollection的功能,但BarCollection不是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. 
}; 

感谢所有的很好的建议和例子!

+0

我已经发布了一个解决方案...主要是为了完整,但因为它涉及'reinterpret_cast'。我强烈建议你考虑重构的选择。尤其是,您将继承引入的“IS-A”关系与代码重用混为一谈。 此外,你不想在那里有'getBars'。暴露你的私人部分被视为暴露主义并受到法律惩罚。 – 2009-11-16 13:07:52

+0

@Matthieu:我已经为这个问题添加了我重构的当前解决方案。谢谢! – 2009-11-16 13:23:10

回答

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

你能不能用集合替换此模板化的在任美孚/酒吧?类似这样

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

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

如果您想提供多态方法,可能不会。 – 2009-11-16 12:12:49

3

问题是你试图混合和匹配两种不同的,pretty-很多独立类型的多态以某种方式行不通。模板的编译时,类型安全多态性将不允许您将基类型替换为派生类型。 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); 
    } 
}; 

注意这类将有与迭代器相同的问题,对向量的操作可能会使此类失效。

又一想

您可能没有意识到这一点,但它可能是完全正常的,只是返回美孚的载体。Bar的用户已经完全了解Foo,因为通过包含Bar.h他们必须通过Bar.h获得Foo.h。原因是Bar从Foo继承,它必须通过Foo.h充分了解该类。我会建议而不是使用上面的解决方案,如果有可能使Foo(或超类Foo)成为接口类并传递指向该接口类的指针向量。这是一个相当常见的模式,不会引起眉毛,我想出了这个不可思议的解决方案:)。然后你可能会有你的理由。祝你好运。

+0

@另外一个想法:好吧,Bar可能会引入你真正想要的功能,而不是自己施放。 – 2009-11-16 12:30:44

3

我会建议从容器类而不是成员容器公开迭代器。这样,容器类型是什么都没有关系。

+0

你不可能是更错误的。取消引用'std :: vector :: iterator'仍然给出'Foo&',这不是您想要的'BarCollection'。 – 2009-11-16 14:47:11

+0

我的意思是自定义迭代器 – 2010-07-09 11:11:50

2

问题是,你为什么要那样做?如果您向用户提供了一组指向Bar的指针,则您认为其中只有Bars,因此内部将指针集合存储到Foo中是没有意义的。如果将Foo的不同子类型存储在指向Foo的指针集合中,则无法将其作为指向Bar的指针集合返回,因为并不是所有对象都有条形图。 在第一种情况下(你知道你只有条),你应该使用上面建议的模板化方法。否则,你不得不重新思考,你真正想要什么。

1

您是否特别需要BarCollection来自FooCollection?因为通常BarCollection不是 a 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,将会发生各种丑陋的事情。

所以通常你想避免这种情况,并没有你的集合类相互派生。另请参阅questionscasting containers的衍生类型,以获得有关此主题的更多答案...

+0

为什么我的初始设计很糟糕的好例子。 +1! – 2009-11-16 13:14:21

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(或从中派生),则会得到一个空指针。

回到你的问题:坏代码。

BarCollection不是0123ta,正如你所提到的那样你很麻烦,因为你可以在指针向量中引入其他元素,即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位。获取NEWshared_ptr对象很容易,并且可以根据需要执行dynamic_cast。获取referenceORIGINALshared_ptr,但被解释为所需的类型...真的不是我想在代码中看到的。

注意
可能有办法做到比使用Boost.Fusion transform_view类更好,但我无法弄清楚。

特别是,使用transform_view我可以得到shared_ptr<Bar>但我不能让一个shared_ptr<Bar>&当我取我的迭代器,这是烦人考虑到仅使用一个参考返回底层vector(而不是const_reference)是以实际修改vector及其包含的对象的结构。

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