Monday, October 19, 2009

2.1 Declarations and Definitions








 

 












2.1 Declarations and Definitions





A

declaration

is the all-encompassing term for anything that tells the compiler

about an identifier. In order to use an identifier, the compiler must

know what it means: is it a type name, a variable name, a function

name, or something else? Therefore, a source file must contain a

declaration (directly or in an #include file) for

every name it uses.







2.1.1 Definitions





A

definition

defines the storage, value, body, or contents of a declaration. The

difference between a

declaration and a definition is that a declaration tells you an

entity's name and the external view of the entity,

such as an object's type or a

function's parameters, and a definition provides the

internal workings of the entity: the storage and initial value of an

object, a function body, and so on.





In a single source file, there can be at most one definition of an

entity. In an entire program, there must be exactly one definition of

each function or object used in the program, except for inline

functions; an inline function must be defined in every source file

that uses the function, and the definitions must all be identical.





A program can have more than one

definition

of a given class, enumeration, inline function, or template, provided

the definitions are in separate source files, and each source file

has the same definition.





These rules are known as the One Definition Rules, or ODR.





Before you can use an entity (e.g., calling a function or referring

to an object), the compiler needs the entity's

declaration, but not necessarily its definition. You can use a class

that has an incomplete declaration in some contexts, but usually you

need a complete definition. (See Chapter 6 for

details about incomplete classes.) The complete program needs

definitions for all the declared entities, but those definitions can

often reside in separate source files. The convention is to place the

declarations for classes, functions, and global objects in a header

file (whose name typically ends with .h or

.hpp), and their definitions in a source file

(whose name typically ends with .cpp,

.c, or .C). Any source file

that needs to use those entities must #include the

header file. Templates have additional complications concerning

declarations and definitions. (See Chapter 7 for

details.)





In this and subsequent chapters, the description of each entity

states whether the entity (type, variable, class, function, etc.) has

separate definitions and declarations, states when definitions are

required, and outlines any other rules pertaining to declarations and

definitions.









2.1.2 Ambiguity





Some language constructs can look like a declaration or an

expression. Such

ambiguities are always resolved in

favor of declarations. A related rule is that a declaration that is a

type specifier followed by a name and empty parentheses is a

declaration of a function that takes no arguments, not a declaration

of an object with an empty initializer. (See

Section 2.6.3 later in this

chapter for more information about empty initializers.) Example 2-1 shows some examples of how declarations are

interpreted.







Example 2-1. Disambiguating declarations


#include <iostream>

#include <ostream>



class T

{

public:

T( ) { std::cout << "T( )\n"; }

T(int) { std::cout << "T(int)\n"; }

};



int a, x;



int main( )

{

T(a); // Variable named a of type T, not an invocation of the T(int)

// constructor

T b( ); // Function named b of no arguments, not a variable named b of

// type T

T c(T(x)); // Declaration of a function named c, with one argument of

// type T

}






The last item in Example 2-1 deserves further

explanation. The function parameter T(x) could be

interpreted as an expression: constructing an instance of

T with the argument x. Or it

could be interpreted as a declaration of a function parameter of type

T named x, with a redundant set

of parentheses around the parameter name. According to the

disambiguation rule, it must be a declaration, not an expression.

This means that the entire declaration cannot be the declaration of

an object named c, whose initializer is the

expression T(x). Instead, it must be the

declaration of a function named c, whose parameter

is of type T, named x.





If your intention is to declare an

object,

not a function, the simplest way to do this is not to use the

function-call style of type cast. Instead, use a keyword cast

expression, such as static_cast<>. (See

Chapter 3 for more information about type casts.)

For example:





T c(static_cast<T>(x)); // Declares an object named c whose initial value is

// x, cast to type T




This problem can crop up when you least expect it. For example,

suppose you want to construct a vector of integers by reading a

series of numbers from the standard input. Your first attempt might

be to use an istream_iterator:





using namespace std;

vector<int> data(istream_iterator<int>(cin), istream_iterator<int>( ));




This declaration actually declares a function named

data, which takes two parameters of type

istream_iterator<int>. The first parameter

is named cin, and the second is nameless. You can

force the compiler to interpret the declaration as an object

definition by enclosing one or more arguments in parentheses:





using namespace std;

vector<int> data((istream_iterator<int>(cin)), (istream_iterator<int>( )));




or by using additional objects for the iterators:





std::istream_iterator<int> start(std::cin), end;

std::vector<int> data(start, end);

















     

     


    No comments:

    Post a Comment