B. Stroustrup - The C++ Programming Language (794319), страница 93
Текст из файла (страница 93)
Consequently, declarations that violate the ODR can be a source ofsubtle errors. Unfortunately, the technique of placing shared definitions in headers and #includeingthem doesn’t protect against this last form of ODR violation. Local type aliases and macros canchange the meaning of #included declarations:// s.h:struct S { Point a; char b; };// file1.cpp:#define Point int#include "s.h"// ...// file2.cpp:class Point { /* ... */ };#include "s.h"// ...The best defense against this kind of hackery is to make headers as self-contained as possible. Forexample, if class Point had been declared in the s.h header, the error would have been detected.A template definition can be #included in several translation units as long as the ODR is adheredto. This applies even to function template definitions and to class templates containing memberfunction definitions.428Source Files and ProgramsChapter 1515.2.4 Standard-Library HeadersThe facilities of the standard library are presented through a set of standard headers (§4.1.2, §30.2).No suffix is needed for standard-library headers; they are known to be headers because they areincluded using the #include<...> syntax rather than #include"...".
The absence of a .h suffix does notimply anything about how the header is stored. A header such as <map> is usually stored as a textfile called map.h in some standard directory. On the other hand, standard headers are not requiredto be stored in a conventional manner. An implementation is allowed to take advantage of knowledge of the standard-library definition to optimize the standard-library implementation and the waystandard headers are handled. For example, an implementation might have knowledge of the standard math library (§40.3) built in and treat #include<cmath> as a switch that makes the standardmath functions available without actually reading any file.For each C standard-library header <X.h>, there is a corresponding standard C++ header <cX>.For example, #include<cstdio> provides what #include<stdio.h> does.
A typical stdio.h will looksomething like this:#ifdef __cplusplus// for C++ compilers only (§15.2.5)namespace std {// the standard librar y is defined in namespace std (§4.1.2)extern "C" {// stdio functions have C linkage (§15.2.5)#endif/* ... */int printf(const char∗, ...);/* ... */#ifdef __cplusplus}}// ...using std::printf;// make printf available in global namespace// ...#endifThat is, the actual declarations are (most likely) shared, but linkage and namespace issues must beaddressed to allow C and C++ to share a header.
The macro __cplusplus is defined by the C++compiler (§12.6.2) and can be used to distinguish C++ code from code intended for a C compiler.15.2.5 Linkage to Non-C++ CodeTypically, a C++ program contains parts written in other languages (e.g., C or Fortran).
Similarly,it is common for C++ code fragments to be used as parts of programs written mainly in some otherlanguage (e.g., Python or Matlab). Cooperation can be difficult between program fragments writtenin different languages and even between fragments written in the same language but compiled withdifferent compilers. For example, different languages and different implementations of the samelanguage may differ in their use of machine registers to hold arguments, the layout of argumentsput on a stack, the layout of built-in types such as strings and integers, the form of names passed bythe compiler to the linker, and the amount of type checking required from the linker.
To help, onecan specify a linkage convention to be used in an extern declaration. For example, this declares theC and C++ standard-library function strcpy() and specifies that it should be linked according to the(system-specific) C linkage conventions:Section 15.2.5Linkage to Non-C++ Code429extern "C" char∗ strcpy(char∗, const char∗);The effect of this declaration differs from the effect of the ‘‘plain’’ declarationextern char∗ strcpy(char∗, const char∗);only in the linkage convention used for calling strcpy().The extern "C" directive is particularly useful because of the close relationship between C andC++.
Note that the C in extern "C" names a linkage convention and not a language. Often, extern"C" is used to link to Fortran and assembler routines that happen to conform to the conventions of aC implementation.An extern "C" directive specifies the linkage convention (only) and does not affect the semanticsof calls to the function.
In particular, a function declared extern "C" still obeys the C++ type-checking and argument conversion rules and not the weaker C rules. For example:extern "C" int f();int g(){return f(1);}// error: no argument expectedAdding extern "C" to a lot of declarations can be a nuisance. Consequently, there is a mechanism tospecify linkage to a group of declarations. For example:extern "C" {char∗ strcpy(char∗, const char∗);int strcmp(const char∗, const char∗);int strlen(const char∗);// ...}This construct, commonly called a linkage block, can be used to enclose a complete C header tomake a header suitable for C++ use.
For example:extern "C" {#include <string.h>}This technique is commonly used to produce a C++ header from a C header. Alternatively, conditional compilation (§12.6.1) can be used to create a common C and C++ header:#ifdef __cplusplusextern "C" {#endifchar∗ strcpy(char∗, const char∗);int strcmp(const char∗, const char∗);int strlen(const char∗);// ...#ifdef __cplusplus}#endif430Source Files and ProgramsChapter 15The predefined macro name __cplusplus (§12.6.2) is used to ensure that the C++ constructs areedited out when the file is used as a C header.Any declaration can appear within a linkage block:extern "C" {// any declaration here, for example:int g1;// definitionextern int g2; // declaration, not definition}In particular, the scope and storage class (§6.3.4, §6.4.2) of variables are not affected, so g1 is still aglobal variable – and is still defined rather than just declared.
To declare but not define a variable,you must apply the keyword extern directly in the declaration. For example:extern "C" int g3;extern "C" { int g4; }// declaration, not definition// definitionThis looks odd at first glance. However, it is a simple consequence of keeping the meaningunchanged when adding "C" to an extern-declaration and the meaning of a file unchanged whenenclosing it in a linkage block.A name with C linkage can be declared in a namespace. The namespace will affect the way thename is accessed in the C++ program, but not the way a linker sees it.
The printf() from std is a typical example:#include<cstdio>void f(){std::printf("Hello, ");printf("world!\n");}// OK// error : no global printf()Even when called std::printf, it is still the same old C printf() (§43.3).Note that this allows us to include libraries with C linkage into a namespace of our choice ratherthan polluting the global namespace. Unfortunately, the same flexibility is not available to us forheaders defining functions with C++ linkage in the global namespace.
The reason is that linkage ofC++ entities must take namespaces into account so that the object files generated will reflect the useor lack of use of namespaces.15.2.6 Linkage and Pointers to FunctionsWhen mixing C and C++ code fragments in one program, we sometimes want to pass pointers tofunctions defined in one language to functions defined in the other. If the two implementations ofthe two languages share linkage conventions and function call mechanisms, such passing of pointers to functions is trivial.
However, such commonality cannot in general be assumed, so care mustbe taken to ensure that a function is called the way it expects to be called.When linkage is specified for a declaration, the specified linkage applies to all function types,function names, and variable names introduced by the declaration(s). This makes all kinds ofstrange – and occasionally essential – combinations of linkage possible. For example:Section 15.2.6Linkage and Pointers to Functionstypedef int (∗FT)(const void∗, const void∗);// FT has C++ linkageextern "C" {typedef int (∗CFT)(const void∗, const void∗);void qsort(void∗ p, size_t n, size_t sz, CFT cmp);}// CFT has C linkage// cmp has C linkagevoid isort(void∗ p, size_t n, size_t sz, FT cmp);void xsort(void∗ p, size_t n, size_t sz, CFT cmp);extern "C" void ysort(void∗ p, size_t n, size_t sz, FT cmp);// cmp has C++ linkage// cmp has C linkage// cmp has C++ linkageint compare(const void∗, const void∗);extern "C" int ccmp(const void∗, const void∗);// compare() has C++ linkage// ccmp() has C linkage431void f(char∗ v, int sz){qsort(v,sz,1,&compare); // errorqsort(v,sz,1,&ccmp);// OKisort(v,sz,1,&compare);isort(v,sz,1,&ccmp);// OK// error}An implementation in which C and C++ use the same calling conventions might accept the declarations marked error as a language extension.
However, even for compatible C and C++ implementations, std::function (§33.5.3) or lambdas with any form of capture (§11.4.3) cannot cross the language barrier.15.3 Using Header FilesTo illustrate the use of headers, I present a few alternative ways of expressing the physical structureof the calculator program (§10.2, §14.3.1).15.3.1 Single-Header OrganizationThe simplest solution to the problem of partitioning a program into several files is to put the definitions in a suitable number of .cpp files and to declare the types, functions, classes, etc., needed forthem to cooperate in a single .h file that each .cpp file #includes. That’s the initial organization Iwould use for a simple program for my own use; if something more elaborate turned out to beneeded, I would reorganize later.For the calculator program, we might use five .cpp files – lexer.cpp, parser.cpp, table.cpp,error.cpp, and main.cpp – to hold function and data definitions.
The header dc.h holds the declarations of every name used in more than one .cpp file:432Source Files and Programs// dc.h:#include <map>#include<string>#include<iostream>namespace Parser {double expr(bool);double term(bool);double prim(bool);}namespace Lexer {enum class Kind : char {name, number, end,plus='+', minus='−', mul='∗', div='/’, print=';', assign='=', lp='(', rp=')'};struct Token {Kind kind;string string_value;double number_value;};class Token_stream {public:Token(istream& s) : ip{&s}, owns(false}, ct{Kind::end} { }Token(istream∗ p) : ip{p}, owns{true}, ct{Kind::end} { }˜Token() { close(); }Token get();Token& current();// read and return next token// most recently read tokenvoid set_input(istream& s) { close(); ip = &s; owns=false; }void set_input(istream∗ p) { close(); ip = p; owns = true; }private:void close() { if (owns) delete ip; }istream∗ ip;// pointer to an input streambool owns;// does the Token_stream own the istream?Token ct {Kind::end};// current_token};extern Token_stream ts;}Chapter 15Section 15.3.1Single-Header Organization433namespace Table {extern map<string,double> table;}namespace Error {extern int no_of_errors;double error(const string& s);}namespace Driver {void calculate();}The keyword extern is used for every variable declaration to ensure that multiple definitions do notoccur as we #include dc.h in the various .cpp files.