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