为什么不总是使用模板而不是实际类型?

时间:2017-05-14 09:45:48

标签: c++

为什么不使用模板而不是实际类型?我的意思是,那么你不必关心你在任何时候处理什么类型,对吧?或者我错了,我们是否有理由使用实际类型,比如int和char?

谢谢!

3 个答案:

答案 0 :(得分:1)

有几个原因。其中一些我现在将列出:

向后兼容性。有些代码库不使用模板,因此您只需替换所有代码即可。

代码中的错误。有时你想确定你得到一个float / int / char或者你有什么让你的代码运行没有错误。现在使用模板然后将类型转换回你需要的东西是公平的假设,但是tat并不总是有效。例如:

#include <iostream>
#include <string>

using namespace std;

void hello(string msg){
    msg += "!!!";
    std::cout << msg << '\n';
}

int main(){
    hello("Hi there"); // prints "Hi there!!!"
}

这很有效。但用这个替换上面的功能是行不通的:

template<typename T>
void hello(T msg){
    msg += "!!!";
    std::cout << msg << '\n';
}

(注意:有些编译器实际上可能会运行上面的代码,但通常你会在评估&#39; operator + =(const char *,char [4])&#39;)时出错。

现在有办法解决这些错误,但有时你只想要一个简单的工作解决方案。

答案 1 :(得分:1)

我认为这是一个过于复杂的问题,永远不会带来好处。

考虑一个简单的类:

class Row {
    size_t len;
    size_t cap;
    int* values;
};

注意:你真的要实例化std::vector<int>,但让我们把它看作一个熟悉的例子......

所以这样看,我们肯定会在values类型的模板中获得一个好处。

template<typename VALUE>
class Row {
    size_t len;
    size_t cap;
    VALUE* values;
};

这是一场大胜利!我们可以编写一个通用的Row类,即使这是(比如说)数学包的一部分,这是一个带有sum()max()等成员的向量空间元组,依此类推我们可以使用其他算术类似longdouble的类型,并构建一个非常有用的模板。

进一步走得怎么样?为什么不参数化lencap成员?

template<typename VALUE,typename SIZE>
class Row {
    SIZE len;
    SIZE cap;
    VALUE* values;
};

我们赢了什么?似乎并非如此。 size_t的目的是表示对象大小的合适类型。您可以使用intunsigned或其他任何东西,但不会获得灵活性(负长度没有意义),您所做的只是任意限制行的大小。

请务必注意,Row的每次使用都必须是模板,并接受SIZE的替代方案。这是我们的Matrix模板:

template<typename VALUE, typename ROW_SIZE, typename COL_SIZE>
class {
    Row< Row<VALUE,ROW_SIZE> , COL_SIZE> rows;
}; 

好的,我们可以通过将ROW_SIZE设为与COL_SIZE相同的类型进行简化,但最终我们通过选择size_t作为尺寸的公分母来实现这一目标。

我们可以把它作为合乎逻辑的结论,程序的切入点将成为:

int main() {
    main<VALUE,SIZE,/*... many many types ...*/,INDEX_TYPE>();
    return EXIT_SUCCESS;
}

其中每个类型决策都是一个参数,并通过所有函数和类线程进入入口点。

这有很多问题:

  1. 这是一场维护噩梦。如果不将其类型决策线程化到入口点,则无法更改或添加到隐藏类。

  2. 这将是一个汇编的噩梦。 C ++编译速度不快,这将使它更糟糕。对于一个大型程序,我可以想象,当编译器解析所有模板的母亲时,你甚至可能会耗尽内存。 [更多关于大型应用程序的问题]

  3. 难以理解的错误消息。有充分理由,编译器很难在模板中提供易于跟踪的错误。借助嵌套在模板中的模板,谁知道这将是一个真正的问题有多深。

  4. 您将无法获得任何有用的灵活性。这些类型最终是相互关联的,许多各种类型都提供了一个很好的答案,无论如何你都不想改变。

  5. 最后,如果您确实有一个您认为是应用程序参数的类型(例如某些数学包中的值类型),参数化的最佳方法是使用typedeftypedef double real_type实际上使整个源代码成为模板,而且整个商店都没有所有模板gubbins。

    您可以typedef float real_typetypedef Rational real_type(其中Rational是一些想象的有理数实现)并真正创建灵活的参数化库。

    但即便如此,你可能也不会typedef size_t size_type或其他因为你不希望改变那种类型。

    总而言之,你最终会做很多工作来提供你不会使用的灵活性,并且拥有诸如库级typedef之类的机制,允许你在不那么显眼的情况下参数化你的应用程序和劳动密集型的方式。

    我会说模板的 草稿指南是“你需要两个吗?”。如果某个函数或类可能具有不同参数的实例,那么答案就是模板。如果您认为已经为应用程序的给定实例修复了类型(或值),则应使用编译时常量和库级typedef

答案 2 :(得分:0)

一个原因是需要为每个具体类型实例化模板,因此,假设您有这样的函数:

void f(SomeObject object, Int x){
  object.do_thing_a(x);
  object.do_thing_b(x);
}

并且Int是模板化的,编译器必须生成foodo_thing_ado_thing_b的一个实例,并且可能还有更多来自do_thing_a和{do_thing_b的函数每Intshortunsigned long long {1}}。有时这甚至会导致实例的组合爆炸。

此外,由于显而易见的原因,您无法创建虚拟成员函数模板。现在,编译器可以在编译整个程序之前了解它应该放入vtable的实例。

顺便说一句,带有类型推断的函数式语言一直在这样做。当你写

f x y = x + y 
在Haskell中,你实际上得到了(非常松散地说)接近C ++的东西

template<class Num, class A>
A f(A x, A y){
    return Num::Add(x, y);
}

然而在Haskell中,编译器没有义务为每个具体的A生成一个实例。