8

使用虚函数和 C++ 继承机制与使用模板和类似 boost 概念之间的关系是什么?

似乎有很多可能的重叠。也就是说,似乎有可能用任何一种方法来实现多态行为。那么,什么时候偏爱一个比另一个有意义呢?

我提出这个的原因是因为我有一个模板化的容器,其中容器本身具有层次关系。我想编写使用这些容器的算法,而不关心它是哪个特定容器。此外,某些算法将受益于知道模板类型满足某些概念(例如,可比较)。

因此,一方面,我希望容器具有多态性。另一方面,如果我想正确实现一些算法,我仍然必须使用概念。初级开发人员应该做什么?

4

5 回答 5

6

我认为概念是一种元界面。他们根据能力对类型进行分类。下一个 C++ 版本提供本地概念。直到我遇到 C++1x 的概念以及它们如何允许将不同但不相关的类型放在一起之前,我才理解它。想象一下你有一个Range界面。您可以通过两种方式对其进行建模。一种是亚型关系

class Range {
    virtual Iterator * begin() = 0;
    virtual Iterator * end() = 0;

    virtual size_t size() = 0;
};

当然,从它派生的每个类都实现 Range 接口并且可以与您的函数一起使用。但现在你看到它是有限的。数组呢?也是一个范围!

T t[N];

begin() => t
end() => t + size()
size() => N

遗憾的是,您不能从实现该接口的 Range 类派生数组。您需要一个额外的方法(重载)。那么第三方容器呢?您的库的用户可能希望将他们的容器与您的函数一起使用。但他不能改变他们容器的定义。在这里,概念进入游戏:

auto concept Range<typename T> {
    typename iterator;
    iterator T::begin();
    iterator T::end();
    size_t T::size();
}

现在,您说一下如果T具有适当的成员函数可以实现的某种类型的受支持操作。在您的库中,您将编写函数泛型。这允许您接受任何类型,只要它支持所需的操作:

template<Range R>
void assign(R const& r) {
    ... iterate from r.begin() to r.end(). 
}

这是一种很好的可替代性任何类型都符合遵循该概念的要求,而不仅仅是那些积极实现某些接口的类型。下一个 C++ 标准更进一步:它定义了一个Container概念,该概念将适合普通数组(通过一些定义某些类型如何适合某些概念的缩放概念图)和其他现有的标准容器。

我提出这个的原因是因为我有一个模板化的容器,其中容器本身具有层次关系。我想编写使用这些容器的算法,而不关心它是哪个特定容器。此外,某些算法将受益于知道模板类型满足某些概念(例如,可比较)。

您实际上可以使用模板来做这两个。你可以保持你的层次关系来共享代码,然后以通用的方式编写算法。例如,传达您的容器具有可比性。这就像实现了标准的随机访问/转发/输出/输入迭代器类别:

// tag types for the comparator cagetory
struct not_comparable { };
struct basic_comparable : not_comparable { };

template<typename T>
class MyVector : public BasicContainer<T> {
    typedef basic_comparable comparator_kind;
};

/* Container concept */
T::comparator_kind: comparator category

实际上,这是一种合理的简单方法。现在您可以调用一个函数,它将转发到正确的实现。

template<typename Container>
void takesAdvantage(Container const& c) {
    takesAdvantageOfCompare(c, typename Container::comparator_kind());
}

// implementation for basic_comparable containers
template<typename Container>
void takesAdvantage(Container const& c, basic_comparable) {
    ...
}

// implementation for not_comparable containers
template<typename Container>
void takesAdvantage(Container const& c, not_comparable) {
    ...
}

实际上有不同的技术可以用来实现它。另一种方法是使用boost::enable_if每次启用或禁用不同的实现。

于 2009-03-07T00:22:17.740 回答
1

是的,这两种机制都可以实现多态行为。事实上,两者都被称为多态性。

虚函数给你动态多态性(因为它是在运行时决定的),而模板给你静态多态性(一切都是在编译时决定的)。

这也应该回答更喜欢哪个的问题。只要有可能,宁愿将工作转移到编译时。因此,当您可以摆脱它时,请使用模板来解决您的多态性需求。当这不可能时(因为您需要使用运行时类型信息,因为在编译时不知道确切的类型),回退到动态多态性。

(当然可能有其他原因偏爱其中一个。特别是,模板要求您将大量代码移动到头文件中,这可能会或可能不会有问题,并且编译速度往往会受到影响,这也可能会或可能不是问题。)

于 2009-03-07T00:14:02.783 回答
0

如果可以在编译时做出决定,请使用模板。否则使用继承和虚函数。

于 2009-03-06T23:57:38.080 回答
0

在这种特定情况下,您可以执行类似的操作

template<typename T>
class ContainerBase{};

template<typename T>
class ContainerDerived : public ContainerBase<T> {};

由于每个“容器”类型对于每个模板类型都是唯一的,因此没有理由不能将每个容器类型的成员函数专门用于模板化类型的特征。

于 2009-03-07T00:18:25.583 回答
0

作为编译时和运行时多态性之间差异的简单示例,请考虑以下代码:

template<typename tType>
struct compileTimePolymorphism
{ };

// compile time polymorphism,
// you can describe a behavior on some object type
// through the template, but you cannot interchange 
// the templates
compileTimePolymorphism<int> l_intTemplate;
compileTimePolymorphism<float> l_floatTemplate;
compileTimePolymorphism *l_templatePointer; // ???? impossible

struct A {};
struct B : public A{};
struct C : public A{};

// runtime polymorphism 
// you can interchange objects of different type
// by treating them like the parent
B l_B;
C l_C:
A *l_A = &l_B;
l_A = &l_C;

当一个对象的行为依赖于另一个对象时,编译时多态性是一个很好的解决方案。在需要更改对象行为的情况下,运行时多态性是必需的。

两者可以通过定义一个多态的模板来组合:

template<typename tType>
struct myContainer : public tType
{};

那么问题是你的容器的行为在哪里需要改变(运行时多态性),以及行为取决于它包含的对象(编译时多态性)。

于 2009-03-07T02:26:10.597 回答