第9章 在实践中使用模板:9.1 包含模型

第9章 在实践中使用模板

 

Template code is a little different from ordinary code. In some ways templates lie somewhere between macros and ordinary (nontemplate) declarations. Although this may be an oversimplification, it has consequences not only for the way we write algorithms and data structures using templates but also for the day-to-day logistics of expressing and analyzing programs involving templates.

模板代码与普通代码是有区别的。从某种意义上讲,模板介于宏和普通(非模板)声明之间。这种说法或许过于简单。但是当我们使用模板来编写算法和数据结构,或者在日常对包含模板的程序进行逻辑分析和表达的时候,我们可能就会认同这种说法。

In this chapter we address some of these practicalities without necessarily delving into the technical details that underlie them. Many of these details are explored in Chapter 14. To keep the discussion simple, we assume that our C++ compilation systems consist of fairly traditional compilers and linkers (C++ systems that don’t fall in this category are rare).

在这一章里,我们只给出模板的一些实际应用,而不会过多研究其背后的技术细节。这些细节的内容会留到第14章去探讨。为了让叙述尽量简洁,假设我们的C++编译系统由传统的编译器和链接器组成(事实上,几乎所有的C++编译器都是由这两者组成)

 

9.1 The Inclusion Model

9.1 包含模型

 

There are several ways to organize template source code. This section presents the most popular approach: the inclusion model.

可以用几种方法来组织模板源代码。本节将给出最常用的方法(包含模型:inclusion model)。

 

9.1.1 Linker Errors

9.1.1 链接器错误

 

Most C and C++ programmers organize their nontemplate code largely as follows:

大多数C/C++程序员都会按如下方式组织代码:

  • Classes and other types are entirely placed in header files. Typically, this is a file with a .hpp (or .H, .h, .hh, .hxx) filename extension.

  类(class)与其他类型(other type)都被放在一个头文件中。通常,头文件的扩展名为.hpp(或者.H、.h、.hh、.hxx)。

  • For global (noninline) variables and (noninline) functions, only a declaration is put in a header file, and the definition goes into a file compiled as its own translation unit. Such a CPP file typically is a file with a .cpp (or .C, .c, .cc, or .cxx) filename extension.

  对于全局变量和(非内联)函数,只有声明放在头文件中,定义则放在一个将被编译为翻译单元的文件中。这类文件的扩展名通常为.cpp(或.C、.c、.cc、.cxx等)。

This works well: It makes the needed type definition easily available throughout the program and avoids duplicate definition errors on variables and functions from the linker.

这样做效果很好:既能够在整个程序中很容易地获得所需类型的定义,同时又避免了链接过程中出现变量和函数重定义的现象。

With these conventions in mind, a common error about which beginning template programmers complain is illustrated by the following (erroneous) little program. As usual for “ordinary code,” we declare the template in a header file:

受这些约定的影响,初学模板的程序员会遇到和抱怨下面(错误的)小程序中出现的常见错误。与“普通代码”一样,我们在头文件中声明模板:

#ifndef MYFIRST_HPP
#define MYFIRST_HPP

// declaration of template
template<typename T>
void printTypeof (T const&);

#endif //MYFIRST_HPP

printTypeof() is the declaration of a simple auxiliary function that prints some type information. The implementation of the function is placed in a CPP file:

printTypeof()是一个简单的辅助函数的声明,它用来打印一类型信息。函数的实现放在CPP文件:

#include <iostream>
#include <typeinfo>
#include "myfirst.hpp"

// 模板的实现/定义
template<typename T>
void printTypeof (T const& x)
{
    std::cout << typeid(x).name() << '\n';
}

The example uses the typeid operator to print a string that describes the type of the expression passed to it. It returns an lvalue of the static type std::type_info, which provides a member function name() that shows the types of some expressions. The C++ standard doesn’t actually say that name() must return something meaningful, but on good C++ implementations, you should get a string that gives a good description of the type of the expression passed to typeid.

这个例子使用typeid运算符来输出一个字符串,它描述了作为参数传递的表达式类型。该运算符返回一个左值静态类型std::type_info,它的name()成员函数用于显示某些表达式的类型。C++标准库实际上并没有要求name()必须返回有意义的结果。但是比较好的C++实现中,应该能够很好地表达传递给typeid的参数类型。

Finally, we use the template in another CPP file, into which our template declaration is #included:

最后,我们在另一个CPP文件中使用该模板,它会include我们这个模板的头文件:

#include "myfirst.hpp"

// use of the template
int main()
{
    double ice = 3.0;
    printTypeof(ice); // call function template for type double
}

A C++ compiler will most likely accept this program without any problems, but the linker will probably report an error, implying that there is no definition of the function printTypeof().

大多数的C++编译器都会顺利地接受这个程序。但是链接时可能会报错,提示找不到printTypeof()函数的定义。

The reason for this error is that the definition of the function template printTypeof() has not been instantiated. In order for a template to be instantiated, the compiler must know which definition should be instantiated and for what template arguments it should be instantiated. Unfortunately, in the previous example, these two pieces of information are in files that are compiled separately.

出现这一错误的原因是因为printTypeOf()函数模板的定义并没有被实例化。为了实例化一个模板,编译器需要知道哪个定义以及基于哪些模板实参进行实例化。遗憾的是,在前面的例子里,这两部分的信息位于分开编译的不同文件里。

Therefore, when our compiler sees the call to printTypeof() but has no definition in sight to instantiate this function for double, it just assumes that such a definition is provided elsewhere and creates a reference (for the linker to resolve) to that definition. On the other hand, when the compiler processes the file myfirst.cpp, it has no indication at that point that it must instantiate the template definition it contains for specific arguments.

当编译器看到printTypeof()调用时,并没看到基于double实例化的函数定义。因此,它会假设其它地方提供了这样的定义,并创建了指向该定义的引用(让链接器去解决这个问题)。另一方面,当编译器处理myfirst.cpp时,却没有任何提示让它用某种具体类型去实例化模板。

 

9.1.2 Templates in Header Files

9.1.2 头文件中的模板

 

The common solution to the previous problem is to use the same approach that we would take with macros or with inline functions: We include the definitions of a template in the header file that declares that template.

前面的问题的常见解决方案是使用与宏或内联函数相同的方法:将模板的定义包含在声明模板的那个头文件中(即让模板的定义和声明位于同一个头文件)。

That is, instead of providing a file myfirst.cpp, we rewrite myfirst.hpp so that it contains all template declarations and template definitions:

也就是说,需要重写myfirst.cpp,让它包含所有模板声明和定义,并将其文件名改为myfirst.hpp:

#ifndef MYFIRST_HPP
#define MYFIRST_HPP

#include <iostream>
#include <typeinfo>

// 模板的声明
template<typename T>
void printTypeof (T const&);

// 模板的实现/定义
template<typename T>
void printTypeof (T const& x)
{
    std::cout << typeid(x).name() << '\n';
}

#endif //MYFIRST_HPP

This way of organizing templates is called the inclusion model. With this in place, you should find that our program now correctly compiles, links, and executes.

这样组织模板的方式被称为“包含模型”。通过使用这个方法,你会发现程序现在可以顺利编译、链接和运行了。

There are a few observations we can make at this point. The most notable is that this approach has considerably increased the cost of including the header file myfirst.hpp. In this example, the cost is not the result of the size of the template definition itself but the result of the fact that we must also include the headers used by the definition of our template—in this case <iostream> and <typeinfo>. You may find that this amounts to tens of thousands of lines of code because headers like <iostream> contain many template definitions of their own.

目前有些问题需要指出。最值得注意的是,这一方法大大增加了包含myfisrt.hpp头文件的成本。在本例中,成本主要不是由模板定义本身的大小导致的,而是由那些为了使用这个模板而必须包含的头文件导致的。如本例用到<iostream>和<typeinfo>,这类头文件可能还会包含它们自己的模板,因此这可能会带来额外的数万行的代码。

This is a real problem in practice because it considerably increases the time needed by the compiler to compile significant programs. We will therefore examine some possible ways to approach this problem, including precompiled headers (see Section 9.3 on page 141) and the use of explicit template instantiation (see Section 14.5 on page 260).

在实际应用中,这是一个很实际的问题。因为它大大增加了编译复杂程序所耗费的时间。我们将在后面章节中给出几种可能的解决方法,包括预编译头文件(见第141页的9.3)以及模板的显式实例化(见第260页14.5节) 。

Despite this build-time issue, we do recommend following this inclusion model to organize your templates when possible until a better mechanism becomes available.

尽管有编译时间的问题,我们还是建议尽可能地使用包含模型来组织模板代码(除非有更好的方法)。

At the time of writing this book in 2017, such a mechanism is in the works: modules, which is introduced in Section 17.11 on page 366. They are a language mechanism that allows the programmer to more logically organize code in such a way that a compiler can separately compile all declarations and then efficiently and selectively import the processed declarations whenever needed.

在2017年编写本书时,一种机制正在研究中:模块。这将在第366页的17.11节中介绍,它们是一种语言机制,允许程序员更合理的组织代码,这样编译器就可以单独编译所有声明,然后在需要时高效地、有选择地导入处理之后的声明。

Another (more subtle) observation about the inclusion approach is that noninline function templates are distinct from inline functions and macros in an important way: They are not expanded at the call site. Instead, when they are instantiated, they create a new copy of a function. Because this is an automatic process, a compiler could endup creating two copies in two different files, and some linkers could issue errors when they find two distinct definitions for the same function. In theory, this should not be a concern of ours: It is a problem for the C++ compilation system to accommodate. In practice, things work well most of the time, and we don’t need to deal with this issue at all. For large projects that create their own library of code, however, problems occasionally show up. A discussion of instantiation schemes in Chapter 14 and a close study of the documentation that came with the C++ translation system (compiler) should help address these problems.

另一个更细微的问题是,使用“包含模型”时,非内联的函数模板与“内联函数和宏”有一个重要的区别:非内联函数模板在调用处不会被展开,而是会被实例化(创建函数的新副本)。由于这是一个自动处理的过程,编译器可能最终会在两个不同的文件中创建两个副本,而有些编译器会发现同一个函数被重定义而报错。理论上我们并不需要关心这个问题:它应该由C++编译系统来解决。而且事实上大多数情况下都不会出现这个问题,我们根本没有必要处理这个问题。但是对于需要创建自身代码库的大项目,可能会偶尔遇到这个问题。第14章关于实例化方案的讨化,以及对C++翻译系统(编译器)附带文档的研究将有助于解决这一问题。

Finally, we need to point out that what applies to the ordinary function template in our example also applies to member functions and static data members of class templates, as well as to member function templates.

最后,我们需要指出的是,在我们例子中应用到普通函数模板的东西,对于类模板的成员函数、静态数据成员以及成员函数模板也同样适用的。

原文链接: https://www.cnblogs.com/5iedu/p/12939738.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    第9章 在实践中使用模板:9.1 包含模型

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/392567

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月3日 下午2:52
下一篇 2023年4月3日 下午2:52

相关推荐