void* 指针的C++替代品(不是模板)

C++ alternatives to void* pointers (that isn't templates)

本文关键字:替代品 指针 C++ void      更新时间:2023-10-16

看起来我对C++有一个根本的误解:<

我喜欢多态容器解决方案。谢谢你,引起我的注意:)


因此,我们需要创建一个相对通用的容器类型对象。它还恰好封装了一些与业务相关的逻辑。然而,我们需要在这个容器中存储本质上任意的数据——从原始数据类型到复杂类的所有数据。

因此,人们会立即想到模板类的概念并完成它。然而,我注意到C++多态性和模板不能很好地结合在一起。由于我们必须处理一些复杂的逻辑,我宁愿只使用模板或多态性,而不是试图通过同时使用这两种方法来对抗C++。

最后,考虑到我想做其中一个,我更喜欢多态性。我发现,像"这个容器包含可比较的类型"这样的约束表示起来要容易得多——一个la java。

让我来谈谈这个问题:在最抽象的情况下,我想象我可以有一个"Container"纯虚拟接口,它类似于"push(void*data)和pop(void*data)"(记录在案,我实际上并没有试图实现堆栈)。

然而,我并不喜欢顶级的void*,更不用说每次我想为具体容器可以使用的数据类型添加约束时,签名都会发生变化。

总结:我们有相对复杂的容器,它们有各种检索元素的方法。我们希望能够改变对可以进入容器的元素的约束。元素应该与多种容器一起使用(只要它们满足特定容器的约束)。

编辑:我还应该提到容器本身需要是多态的。这是我不想使用模板化C++的主要原因。

那么,我应该放弃对Java类型接口的热爱,转而使用模板吗?我应该使用void*并静态投射所有内容吗?或者我应该使用一个空的类定义"Element",它什么都不声明,并将其用作"Element"层次结构中的顶级类?

我喜欢堆栈溢出的原因之一是,许多响应提供了一些我甚至没有考虑过的其他方法的有趣见解。因此,提前感谢您的见解和评论。

如果您将真正任意的数据存储到容器中,则可以考虑使用boost::any的标准容器。

听起来更像是你想要一个类似boost::ptr_container的东西,其中可以存储在容器中的任何东西都必须从某个基类型派生,而容器本身只能为你提供对基类型的引用。

简单的事情是定义一个名为Container的抽象基类,并为您可能希望存储的每种项目将其子类化。然后,您可以使用任何标准集合类(std::vectorstd::list等)来存储指向Container的指针。请记住,由于要存储指针,因此必须处理它们的分配/释放。

然而,您需要一个单独的集合来存储如此不同类型的对象,这表明您的应用程序的设计可能存在问题。在实现这个超级通用容器之前,最好重新访问业务逻辑。

如果使用得当,多态性和模板在一起确实很好。

无论如何,我理解您希望在每个容器实例中只存储一种类型的对象。如果是,请使用模板。这将防止您错误地存储错误的对象类型。

至于容器接口:根据您的设计,也许您也可以将它们模板化,然后它们将具有类似void push(T* new_element)的方法。当你想把对象添加到一个容器(未知类型)中时,想想你对它的了解。这个物体最初从哪里来?返回void*的函数?你知道它会是可比的吗?至少,如果所有存储的对象类都在代码中定义,那么可以让它们都继承自一个共同的祖先,比如Storable,并使用Storable*而不是void*

现在,如果您看到对象总是通过像void push(Storable* new_element)这样的方法添加到容器中,那么将容器作为模板实际上不会有任何附加值。但你会知道它应该存储Storables。

你能不能没有一个包含元素的根容器类:

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

首先,模板和多态性是正交的概念,它们在一起确实很好。接下来,你为什么想要一个特定的数据结构?STL或boost数据结构(特别是指针容器)对您不起作用。

考虑到你的问题,听起来你在这种情况下会滥用继承权。可以对容器中的内容创建"约束",尤其是在使用模板的情况下。这些约束可能超出编译器和链接器所能提供的范围。事实上,对于继承这类事情来说更为尴尬,错误更有可能留给运行时。

使用多态性,基本上只剩下容器的基类和数据类型的派生类。基类/派生类可以在两个方向上都具有所需的虚拟函数。

当然,这意味着您还需要将原始数据类型封装在派生类中。如果你想重新考虑模板的整体使用,这就是我使用模板的地方。从作为模板的基中生成一个派生类,并将其用于基元数据类型(以及其他不需要模板提供的更多功能的类型)。

不要忘记,通过为每个模板化类型进行typedef,您可能会让您的生活更轻松——尤其是当您以后需要将其中一个类型转换为类时。

您可能还想查看Boost概念检查库(BCCL),该库旨在为模板类的模板参数提供约束,在这种情况下是您的容器。

只是重申一下其他人所说的,我从来没有遇到过混合多态性和模板的问题,我已经用它们做了一些相当复杂的事情。

您不必放弃类似Java的接口,也不必使用模板。Josh关于通用基本模板Container的建议肯定会允许您以多态的方式传递Container及其子级,但另外,您当然可以将接口实现为抽象类,作为包含的项。你没有理由不能像你建议的那样创建一个抽象的IComparable类,这样你就可以拥有如下的多态函数:

class Whatever
{
   void MyPolymorphicMethod(Container<IComparable*> &listOfComparables);
}

这个方法现在可以接受Container的任何子级,该子级包含实现IComparable的任何类,因此它将非常灵活。