这太长了,但可能会提供一些信息。
Java 中的泛型是一种类型擦除机制,以及类型转换和类型检查的自动代码生成。
template
C++ 中的 s 是代码生成和模式匹配机制。
你可以使用C++template
只需付出一点努力就能完成 Java 泛型所做的事情。std::function< A(B) >
以协变/逆变方式表现A
and B
类型和转换为其他std::function< X(Y) >
.
但两者的主要设计并不相同。
A Java List<X>
将是一个List<Object>
上面有一些薄的包裹,因此用户不必在提取时进行类型转换。如果您将其作为List<? extends Bar>
,它再次得到一个List<Object>
本质上,它只是有一些额外的类型信息,这些信息改变了强制转换的工作方式以及可以调用哪些方法。这意味着您可以从List
into a Bar
并知道它有效(并检查它)。只为所有生成一种方法List<? extends Bar>
.
A C++ std::vector<X>
本质上不是一个std::vector<Object>
or std::vector<void*>
或其他任何东西。 C++ 的每个实例template
是不相关的类型(模板模式匹配除外)。实际上,std::vector<bool>
使用与任何其他完全不同的实现std::vector
(现在这被认为是一个错误,因为在这种情况下,实现差异以令人讨厌的方式“泄漏”)。每个方法和函数都是针对您传递给它的特定类型独立生成的。
在 Java 中,假设所有对象都适合某种层次结构。在 C++ 中,这有时很有用,但人们发现它通常不适合解决问题。
C++ 容器不需要继承公共接口。 Astd::list<int>
and std::vector<int>
是不相关的类型,但您可以对它们进行统一操作——它们都是顺序容器。
“参数是一个顺序容器吗”这个问题是一个好问题。这允许任何人实现顺序容器,并且这种顺序容器可以与具有完全不同实现的手工编写的 C 代码一样高性能。
如果您创建了公共根std::container<T>
所有容器都继承自该容器,它要么装满virtual
表格混乱,否则除了作为标签类型之外它毫无用处。作为一种标签类型,它会侵入性地将自身注入到所有非std
容器,要求它们继承自std::container<T>
成为一个真正的容器。
相反,特征方法意味着对容器(顺序的、关联的等)是什么有规范。您可以在编译时测试这些规范,和/或允许类型注意到它们通过某种特征符合某些公理。
C++03/11 标准库使用迭代器来完成此操作。std::iterator_traits<T>
是一个特征类,它公开有关任意类型的迭代器信息T
。完全与标准库无关的人可以编写自己的迭代器,并使用std::iterator<...>
自动工作std::iterator_traits
,手动添加自己的类型别名,或者专门化std::iterator_traits
传递所需的信息。
C++11 更进了一步。for( auto&& x : y )
可以处理在设计基于范围的迭代之前很久编写的内容,而无需触及类本身。你只需写一个免费的begin
and end
该类所属的命名空间中的函数返回一个有效的前向迭代器(注意:即使是足够接近的无效前向迭代器也可以工作),然后突然for ( auto&& x : y )
开始工作。
std::function< A(B) >
是将这些技术与类型擦除一起使用的示例。它有一个构造函数,接受任何可以复制、销毁、调用的内容(B)
其返回类型可以转换为A
。它可以采用的类型可以是完全无关的——仅测试所需的类型。
因为std::function
根据设计,我们可以使用 lambda 可调用函数,它们是不相关的类型,可以将其类型擦除为通用类型std::function
如果需要,但当没有类型擦除时,可以从那里知道它们的调用操作。所以一个template
采用 lambda 的函数知道在调用时会发生什么,这使得内联成为一个简单的本地操作。
这种技术并不新鲜——它是在 C++ 中出现的std::sort
,一种比 C 更快的高级算法qsort
由于内联作为比较器传递的可调用对象很容易。
简而言之,如果您需要通用的运行时类型,请输入擦除。如果您需要某些属性,请测试这些属性,不要强制使用公共基础。如果您需要保留某些公理(不可测试的属性),请记录或要求调用者通过标签或特征类声明这些属性(请参阅标准库如何处理迭代器类别 - 再次强调,而不是继承)。如有疑问,请使用启用了 ADL 的自由函数来访问参数的属性,并让默认自由函数使用 SFINAE 来查找方法并调用(如果存在),否则会失败。
这种机制消除了公共基类的中心责任,允许现有类无需修改即可适应您的要求(如果合理),仅在需要的地方放置类型擦除,避免virtual
开销,并且理想情况下当发现属性不成立时会生成明显的错误。
If your ENGINE
有某些需要传递的属性,编写一个测试这些属性的特征类。
如果存在无法测试的属性,请创建描述此类属性的标签。使用特征类的专门化或规范的 typedef,让类描述该类型适用的公理。 (参见迭代器标签)。
如果你有这样的类型ENGINE_BASE
,不要要求它,而是使用它作为所述标签和特征以及公理 typedef 的帮助器,例如std::iterator<...>
(你永远不必继承它,它只是充当助手)。
避免过度指定要求。如果usually_important
永远不会在你的Worker<X>
,可能是你的X
不需要b
在这种情况下。但要以比“方法无法编译”更清晰的方式测试属性。
有时,只是下注。遵循这些做法可能会让事情变得更困难——所以采取更简单的方法。大多数代码写完后就被丢弃。了解您的代码何时会持续存在,并编写得更好、更具可扩展性和更可维护性。要知道,您需要在一次性代码上练习这些技术,以便在必要时可以正确编写代码。