- Overview
-
Table of Contents
- Special Member Functions: Constructors, Destructors, and the Assignment Operator
- Operator Overloading
- Memory Management
- Templates
- Namespaces
- Time and Date Library
- Streams
- Object-Oriented Programming and Design Principles
- The Standard Template Library (STL) and Generic Programming
- Exception Handling
- Runtime Type Information (RTTI)
- Signal Processing
- Creating Persistent Objects
- Bit Fields
- New Cast Operators
- Environment Variables
- Variadic Functions
- Pointers to Functions
- Function Objects
- Pointers to Members
- Lock Files
- Design Patterns
- Dynamic Linking
-
Tips and Techniques
- Using the Swap() Algorithm
- Using class stopwatch for Performance Measurements
- Extending <tt><iostream></tt> to Support User-Defined Types
- Using <tt>auto_ptr</tt> To Automate Memory Management
- Using <tt>auto_ptr</tt> To Automate Memory Management, Part II
- Using <tt>auto_ptr</tt> To Automate Memory Management, Part III
- Using <tt>enum</tt>s as Mnemonic Indexes
- Create Objects on Pre-Allocated Memory Using <tt>Placement-new</tt>
- Online Books: <tt>Placement-new</tt>
- Bitwise Operators
- Bitwise Operators II
- Who's <tt>this</tt>?
- A Reference Guide
- The Virtues of Multiple Inheritance
- Interfaces
- Multiple Inheritance: Construction and Destruction Order
- nothrow new
- POD Initialization
- Object Initialization
- <tt>const</tt> Declarations
- The Semantics of <tt>volatile</tt>
- <tt>inline</tt> Functions
- Project Organization Guidelines
- All About <tt>bool</tt>
- <tt>typedef</tt> Declarations
- State of the <tt>union</tt>
- Dynamic Cast Uses
- Integrating C and C++
- <tt>const</tt>-Correctness
- <tt>const</tt>-Correctness: Advanced Issues
- Sprucing Up Legacy Code
- Virtual Constructors
- Naming Names
- Function Calls
- Speaking Standardese (updated)
- Speaking Standardese: the One Definition Rule
- Declarations and Definitions
- More on Declarations and Definitions
- The Most Vexing Parse
- Finally, At Last
- Sound Bytes (Admittedly Off Topic)
- Local Classes
- Complex Arithmetic
- Floating Point Woes
- String Manipulation
- The Object Model
- The Object Model II
- The Object Model III
- Temporary Objects
- Temporary Objects: Advanced Techniques
- Over-Engineering
- Security Enhancements
- Drop the (automatic) Pilot
- Choosing the Right Container
- Choosing the Right Container II
- Choosing the Right Container, Part III
- Arrays and Pointers
- Low-Level File I/O
- Low-Level File I/O Part II
- static Declarations, Part I
- static Declarations, Part II
- <code>static</code> Initialization Order
- Revisiting the Deprecation of File-Scope Static
- Virtual Memory and Memory Mapping
- Cellular Phone Programming Guidelines
- The Handle/Body Idiom
- Whole Program Optimization, Part I
- Whole Program Optimization, Part II
- Manipulating Directories
- Window Dressing
- <code>friend</code> Declarations
- <code>friend</code> Part II: the Interaction of Friendship and Template Classes
- Forcing Object Allocation on Specific Storage Types
- Lazy Evaluation
- Cache and Carry
- Controlling a Container’s Capacity
- Non-Blocking I/O, Part I
- Non-Blocking I/O, Part II
- Using Unions for Automatic Conversion
- Launching a Child Process
- <tt>switch</tt> Statements
- Introducing the "struct Hack"
- Scoped Enumerators
- Doing Statistics with STL
- Fixing the "Unresolved External" Linkage Error
- Understanding Calling Conventions
- Understanding the Empty Base Optimization
- Implementing RPC with the door Library, Part 1
- Implementing RPC with the door Library, Part 2
- Eliminating Two Common Pointer and <tt>sizeof</tt> Bugs
- Command Line Arguments
- Performance Myths Busting
- Tag Names And Types Part I
- Tag Names And Types Part II
- The Infamous goto
- Trimming Strings
- Can Objects Live Forever? Part I
- Can Objects Live Forever? Part II
- Five Ways to Improve Your Functions
- Member Aggregate Initialization
- Five Futile Coding-Style Debates
- The Good Parasite Idiom: An Exercise in OOD
- The Good Parasite Idiom: An Exercise in OOD, Part II
- The Good Parasite Idiom: An Exercise in OOD, Part III
- Ten Techniques to Reduce the Size of Your Classes, Part I
- Ten Techniques to Reduce the Size of Your Classes, Part II: Inheritance Issues
- Ten Techniques to Reduce the Size of Your Classes, Part III
- Ten Techniques to Reduce the Size of Your Classes IV
- Taking the Address of an Object with an Overloaded Operator <tt>&</tt>
- strcpy() -- How and Why Does It "Just Work"?
- Anonymous Structs
- Five Easy Ways to Reduce The Size of your Executables
- Standard Layout Classes and Trivially Copyable Types, Part I
- Standard Layout Classes and Trivially Copyable Types, Part II
- Five Simple Code Sanity Checks
- Five Things You Need to Know About C++11 Unions
- A Tour of C99
- A Tour of C1X
- C++0X: The New Face of Standard C++
- C++0x Concurrency
- The Reflecting Circle
- We Have Mail
- The Soapbox
- Numeric Types and Arithmetic
- Careers
- Locales and Internationalization
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.
