继承和模板参数

3

我有一个关于模板参数的简单问题。我正在编写一个使用列表的Scheduler类,如下所示:

list<PartitionT<CompareJobReady, CompareJobRunning> > partitions;

PartitionT是一个使用优先队列的模板类,我希望能够通过比较器类来参数化这些队列。CompareJobReady和CompareJobRunning是这些类(它们实现了一个特定的operator())。
由于PartitionT是一个模板类,因此我希望能够传递任何类型的比较器类。特别地,我定义了两个额外的类,即CompareJobReadyEDFVD(继承自CompareJobReady)和CompareJobRunningEDFVD(继承自CompareJobRunning)。
现在我想要做的事情,就是能够像这样写代码:
list<PartitionT<CompareJobReady, CompareJobRunning> > *p = new list<PartitionT<CompareJobReadyEDFVD, CompareJobRunningEDFVD> >();

但编译器告诉我转换不可能。有什么最好的解决方案吗?

1
这是来自标准库的 std::list 吗? - dyp
是的,抱歉如果我没有提到它。 - user1809140
2个回答

1

标准库的容器类拥有它们的元素。也就是说,如果你有:

class Base {};
class Child : public Base {};

std::list < Base > myList;
std::list < Base > *pMyList;

那么myList的元素只能作为Base类型的对象(引用)进行访问。您可以存储Base类型的元素(例如push_backemplace_back),并获取引用/副本(例如front或通过迭代器),例如,请参见cppreference/list。让我们来看一下push_back

void push_back(const value_type&);

其中value_type是您传递给std::list的第一个模板参数。在您的情况下,它是PartitionT < CompareJobReady, CompareJobRunning >,或者在上面的例子中,它是Basepush_back实际上会复制您传递的参数,并将该副本作为新元素。为什么呢?因为新元素可以由列表拥有。当列表本身被销毁时,列表可以销毁此元素,并将其传递给另一个列表(如果您移动/交换两个列表)。如果未复制该元素并且从外部销毁了该元素,则列表将包含已销毁的元素-这将破坏列表所提供的保证(而且这样做不好)。

另一个例子(简化,不太准确):list通过分配器为其元素分配内存。这里的默认分配器是std::allocator<Base>。它只为存储类型为Base的对象所需的内存分配了足够的内存。

当你执行像pMyList = new std::list < Child >;这样的操作时,右侧会产生一个“指向std::list<Child>的指针”,而左侧的类型是“指向std::list<Base>的指针”。这两种类型是不相关的,因为std::list<Base> 没有继承或定义到std::list<Child>的转换。这有一些原因是相当好的,其中之一是通用编程需要知道它所处理的类型。多态性是一种抽象,使您无需 - 也不能 - 在编译时知道您正在处理的类型。
C++中的多态性通过指针或引用工作。因此,如果您想将一些Child对象排列在与此类型无关的列表中(例如,它只知道Base类型),则必须将它们存储为指针:
std::list < std::shared_ptr<Base> > myList2;

myList2.push_back( new Child );    // better not use, there's a caveat (1)

// approach w/o this caveat
std::shared_ptr<Base> pNewChild( new Child );    // or make_shared
myList2.push_back( pNewChild );

请注意,我在这里使用了一个shared_ptr,如果适合您的目的,您也可以使用unique_ptr,但是您不应该使用原始指针:因为myList2拥有其shared_ptr元素,并且shared_ptr持有共享所有权的对象(类型为Base),myList2间接拥有您在列表中存储指针的Child对象。由于原始指针不能表达所有权,例如,不清楚谁负责销毁它们。了解更多关于"零规则"的信息。
(1)有一个警告,请参阅boost,尽管它不影响此示例。

如果您真的想通过选择某个比较器类来进行通用编程,那么您"必须坚持在编译时": 您的列表类型(*p的类型)不应该固定为list<Base>,而应该是通用的(使用模板),并且您使用的所有算法也都必须是通用的。您不能(简单地*)混合通用编程和运行时类型选择,因为通用编程的全部内容都是关于在编译时创建代码。

*有一种hack方法可以实现,滥用RTTI,因此非常缓慢。


0

使用模板来定义类型,但使用继承来实现行为。提供一个构造函数,可以将构造好的比较器类(作为模板参数类型)传入其中,例如:

list<PartitionT<CompareJobReady, CompareJobRunning> > *p = 
  new list<PartitionT<CompareJobReady, CompareJobRunning> >( 
    new CompareJobReadyEDFVD(), new CompareJobRunningEDFVD());

如果这是一个std list,那么list的构造函数是不正确的。您可以使用初始化列表(7)。 - dyp
我不确定这是否解决了我的问题,因为我必须将类本身传递给priority_queue,而不是类的实例: - user1809140
但是比较器是一个完全独立的类模板参数,与 std::priority_queue 不同。在您的 PartitionT 模板类中是否使用它?如果没有,请将其删除。 - Pontus Gagge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接