Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Declarations and Definitions

Last updated Jan 1, 2003.

Declarations and Definitions

Continuing last week's brief course in standardese, it's time for to explain the differences between two similar but not identical terms: namely, declarations and definitions.

A Source of Confusion

Declarations and definitions have different meanings in different contexts. In the case of class objects and variables (to which I collectively refer as "objects"), a definition causes the compiler to allocate storage for an object, whereas a declaration merely associates an object with a certain type, without allocating storage for it.

The problem is that the two terms overlap to some extent. Ddefinitions also serve as declarations, because they inject an identifier of a certain type to a scope. However, a declaration isn't a definition because it doesn't entail storage allocation for the declared object. To add to the confusion, the semantics of definitions and declarations is slightly different when applied to types and functions, as I will show momentarily. So let's look at a more detailed analysis of these two terms.

Objects

In C++, objects must be declared before you can use them. A declaration, as previously noted, merely binds an identifier to a type. For example:

//file1.cpp declarations
extern int num;
extern struct tm t;
extern const long val;

In this example, num, t, and val are declared but their definitions appear in another translation unit. Such declarations are necessary when you access a global object from several translation units. Of course, one of the program's translation units must contain definitions for these objects:

//file2.cpp definitions
int num;
struct tm t;
extern const long val=100; //initialized, hence -- a definition

During the linkage phase, the linker resolves references to the same object that appear in separate translation units. It knows, for instance, that the identifier num declared in file1.cpp refers to the same num that is defined file2.cpp. That's because the name, linkage, scope, and type of the two match. This leads us to an important observation:

NOTE

An object may be declared an unlimited of times within the same program (or even inside the same translation unit). However, it must be defined exactly once within the same program. (Later, we will see that this rule has a few exceptions.)

Defining (rather than merely declaring) objects before their usage is a more common scenario:

int main()
{
 //definitions
 int num=99;
 string URL; 
 void *p=0;
 const int val=100;
 char name[12]={0};
//..
}

These are definitions because the compiler allocates storage for the objects (in the case of objects with static storage types, it's the linker's job to allocate storage -- but let's ignore these technicalities for now). Another crucial difference between a declaration and a definition is that the latter may contain an explicit initializer. That said, the definitions above are also declarations since they bind the objects (such as num and URL) to certain types. To conclude, the four objects above are declared and defined at once.

Types

A type definition binds an identifier to a user-defined class, struct, union or enum. The definition includes the members of the said identifier:

class Currency //a class definition
{
 int dollars, cents;
 Locale l;
public:
 Currency(int d, int c);
 ~Currency();
};
enum Dir //an enum definition
{
 up, 
 down
};

Here again, a definition also serves as a declaration because it binds a name, say Currency, to a user-defined type that the compiler can recognize henceforth. Type definitions don't entail storage allocation because types are a compile-time concept; they have no representation at runtime and therefore never occupy memory. By contrast, objects are a runtime concept; the executable image memory slots for them. Thus, you can think of a type definition as "all the information that the compiler needs in order to allow me to create an instance of the said type." For example:

//possible only after Currency has been defined
Currency c; 
static Currency c2;
Currency *p= new Cuurency;

It is possible to declare a type, without defining one. Such declarations are often referred to as forward declarations. They are needed when you refer to a type before the compiler has seen its definition:

class Currency; //forward declaration
enum Dir; //forward declaration

class Locale
{
 Dir & d;
 Currency * c;
};

class Currency //a class definition
{
 int dollars, cents;
 Locale l;
public:
 Currency(int d, int c);
 ~Currency();
};

In this example, the declaration of Currency is necessary because Locale contains a reference to Currency. Notice that because Currency was declared but not defined, you can only create pointers and references to it. An attempt to instantiate it or use it in a sizeof expression will cause a compilation error.

Functions

A function definition contains both a prototype and a body, or implementation. For example:

void increment(int &n) //function definition
{
 n++;
}

A function declaration contains only the prototype followed by a semicolon:

void increment(int &n); //declaration

You can have multiple declarations of the same function but only one definition thereof must exist in the entire program. There are two exceptions to this rule, though.

  • inline functions (whether explicitly declared inline or member functions defined inside a class body) have to be defined in every translation unit that uses them. The reason is obvious; to inline a function, the compiler must see its definition.

  • Templates are the second exception. A template (either a class template or a function template) must be defined in every translation unit in which it is used. Again, the compiler must see the template's definition in order to generate code for every specialization used in a translation unit.

The common practice is to define templates and inline functions in a header file and #include it in every translation unit that uses them. This is a common pitfall among novices, who assume that it's enough to provide declarations of templates in a header file and define them in a separately compiled translation unit.

If you're concerned about the size of the resultant executable, fret not. Linkers are usually clever enough to remove redundant copies of the same specialization from the resulting executable.

Summary

Although the two terms are used interchangeably in textbooks and articles, declarations and definitions aren't the same thing. The distinction between the two is chiefly important when dealing with templates and inline functions. A special type of a declaration which I didn't discuss here is called a using declaration. You can read about its uses in the namespaces section.