void *指针(不是模板)的C ++替代方法
看来我对C ++有基本的误解:
我喜欢多态容器解决方案。谢谢你,让我引起注意:)
因此,我们需要创建一个相对通用的容器类型对象。它还碰巧封装了一些与业务相关的逻辑。但是,我们需要在该容器中存储基本上任意的数据,从原始数据类型到复杂类。
因此,将立即跳到模板类的想法并完成它。但是,我注意到C ++多态性和模板不能很好地配合使用。考虑到我们需要处理一些复杂的逻辑,我宁愿坚持使用模板还是多态性,而不要尝试通过使两者同时使用来对抗C ++。
最后,鉴于我想做一个或者另一个,我更喜欢多态。我发现用Java来表示诸如"此容器包含可比较类型"之类的约束要容易得多。
带给我一个问题:最抽象的说,我想我可以拥有一个"容器"纯虚拟接口,该接口类似于" push(void * data)和pop(void * data)"(用于记录) ,我实际上并不是在尝试实现堆栈)。
但是,我并不是非常喜欢顶层的void *,更不用说每次我想对具体容器可以使用的数据类型添加约束时,签名都会改变的情况。
总结:我们有相对复杂的容器,它具有各种检索元素的方式。我们希望能够改变对可以放入容器的元素的约束。元素应与多种容器一起工作(只要它们满足该特定容器的约束)。
编辑:我还应该提到容器本身需要是多态的。这是我不想使用模板化C ++的主要原因。
那么,我应该放弃对Java类型接口的热爱并选择模板吗?我应该使用void *并静态转换所有内容吗?还是应该使用不声明任何内容的空类定义"元素",并将其用作"元素"层次结构中的顶级类?
我之所以喜欢堆栈溢出,原因之一是许多响应对我什至没有考虑过的其他方法提供了一些有趣的见解。因此,在此先感谢见解和评论。
解决方案
如果正确使用多态性和模板,它们可以很好地协同工作。
无论如何,我知道我们只想在每个容器实例中存储一种类型的对象。如果是这样,请使用模板。这将防止我们错误地存储错误的对象类型。
至于容器接口:取决于设计,也许我们也可以使它们模板化,然后它们将具有`void push(T * new_element)之类的方法。想将对象添加到(未知类型的)容器中时,我们将对该对象有什么了解。该对象最初来自何处?返回" void *"的函数?我们知道它是可比的吗?至少,如果在代码中定义了所有存储的对象类,则可以使它们全部继承自一个共同的祖先,例如" Storable",并使用" Storable *"代替" void *"。
现在,如果我们看到对象总是通过" void push(Storable * new_element)"之类的方法添加到容器中,那么将容器作为模板确实没有任何添加值。但是,我们将知道它应该存储可存储对象。
简单的事情是定义一个称为"容器"的抽象基类,并为我们可能希望存储的每种商品子类化。然后,我们可以使用任何标准集合类(" std :: vector"," std :: list"等)来存储指向"容器"的指针。请记住,由于要存储指针,因此必须处理它们的分配/取消分配。
但是,我们只需要一个集合来存储这种类型迥异的对象,这一事实表明,应用程序设计可能有问题。在实现此超通用容器之前,最好重新访问业务逻辑。
如果要将真正任意的数据存储到容器中,可以使用标准的boost :: any容器。
听起来更像是我们希望拥有一个boost :: ptr_container之类的东西,其中可以存储在容器中的任何东西都必须源自某个基本类型,并且容器本身只能为我们提供对基本类型的引用。
使用多态性,基本上只剩下容器的基类和数据类型的派生类。基类/派生类可以在两个方向上具有任意数量的虚函数。
当然,这意味着我们还需要将原始数据类型包装在派生类中。如果我们要重新考虑整体模板的使用,这就是我要使用模板的地方。从作为模板的基础创建一个派生类,并将其用于原始数据类型(以及其他一些不需要模板提供的功能的类)。
不要忘记,通过每种模板化类型的typedef可以使生活更轻松-特别是如果以后需要将其中一种转换为类时,尤其如此。
首先,模板和多态性是正交的概念,它们可以很好地协同工作。接下来,为什么要特定的数据结构? STL或者boost数据结构(特别是指针包含器)对我们不起作用。
给定问题,听起来我们会在情况中滥用继承。可以对容器中的内容创建"约束",尤其是在使用模板的情况下。这些限制可能超出了编译器和链接器所能提供的范围。那种带有继承的东西实际上更尴尬,并且错误更可能留给运行时。
我们是否不能具有包含元素的根Container类:
template <typename T> class Container { public: // You'll likely want to use shared_ptr<T> instead. virtual void push(T *element) = 0; virtual T *pop() = 0; virtual void InvokeSomeMethodOnAllItems() = 0; }; template <typename T> class List : public Container<T> { iterator begin(); iterator end(); public: virtual void push(T *element) {...} virtual T* pop() { ... } virtual void InvokeSomeMethodOnAllItems() { for(iterator currItem = begin(); currItem != end(); ++currItem) { T* item = *currItem; item->SomeMethod(); } } };
然后可以将这些容器多态传递:
class Item { public: virtual void SomeMethod() = 0; }; class ConcreteItem { public: virtual void SomeMethod() { // Do something } }; void AddItemToContainer(Container<Item> &container, Item *item) { container.push(item); } ... List<Item> listInstance; AddItemToContainer(listInstance, new ConcreteItem()); listInstance.InvokeSomeMethodOnAllItems();
这以类型安全的通用方式为我们提供了Container接口。
如果要对可包含的元素类型添加约束,则可以执行以下操作:
class Item { public: virtual void SomeMethod() = 0; typedef int CanBeContainedInList; }; template <typename T> class List : public Container<T> { typedef typename T::CanBeContainedInList ListGuard; // ... as before };
我们可能还需要检出Boost Concept检查库(BCCL),该库旨在提供对模板化类的模板参数(在这种情况下为容器)的约束。
只是重申一下其他人所说的话,我从来没有遇到过将多态和模板混合在一起的问题,并且我对它们做了一些相当复杂的工作。
我们不必放弃类似Java的界面,也可以使用模板。 Josh关于通用基本模板Container的建议当然可以使我们多态地传递Container及其子对象,但是除此之外,我们当然可以将接口实现为抽象类,以将其包含在内。没有理由不能像我们建议的那样创建抽象的IComparable类,这样我们就可以具有如下所示的多态函数:
class Whatever { void MyPolymorphicMethod(Container<IComparable*> &listOfComparables); }
现在,此方法可以接受包含实现IComparable的任何类的Container的任何子级,因此它将非常灵活。