본문으로 건너뛰기

Preliminaries

This chapter covers the fundamental C++ and object-oriented design concepts required for large-scale software design.

1.1 Multi-File C++ Programs

Multi-File Program:
A program whose source code is divided among multiple files.

Separating cohesive parts of a program improves recompilation efficiency and allows components to be reused without copying source code.

Translation Unit:
The source produced after a source file and all recursively included headers are processed by the preprocessor.

Source File + Included Headers

Preprocessing

Translation Unit

Compilation

Object File

1.1.1 Declaration versus Definition

Declaration:
Introduces a name into a program.

Definition:
Provides the unique description of an entity such as a type, object, or function.

A declaration can generally be repeated in the same scope, while an entity must have exactly one definition.

A declaration is not a definition when it:

  • Declares a function without a body.
  • Uses extern without an initializer or function body.
  • Declares a static class data member inside a class.
  • Declares a class name.
  • Is a typedef declaration.
int f(int, int);

extern int globalVariable;

class Point;

typedef int Int;

Definitions provide the actual entity.

int x;

enum Color
{
RED,
GREEN,
BLUE
};

class Stack
{
};

Key Distinction:

Declaration
└── Introduces a name

Definition
└── Describes the entity

1.1.2 Internal versus External Linkage

Linkage:
Determines how names in different translation units interact during linking.

The book distinguishes two kinds:

  • Internal Linkage
  • External Linkage

Internal Linkage:
A name is local to one translation unit and cannot collide with an identical name in another translation unit.

static int count;

A definition with internal linkage is limited to its translation unit.

External Linkage:
A name can interact with other translation units at link time.

Definitions with external linkage can resolve symbols referenced from other translation units.

Typical examples include non-inline member functions and non-static free functions.

Point& Point::operator+=(const Point& right)
{
// ...
}

Point operator+(const Point& left, const Point& right)
{
// ...
}

Key Distinction:

Internal Linkage
└── One Translation Unit

External Linkage
└── Multiple Translation Units

A declaration itself does not create an object-file symbol.

The use of a declared external entity is what can produce an unresolved symbol that must later be resolved by the linker.

1.1.3 Header (.h) Files

Header File:
A file containing declarations and definitions that must be visible to multiple translation units.

The book's main rule is:

Do not place definitions with external linkage in header files.

If the same header is included by multiple translation units, such definitions can produce multiple-definition linker errors.

Typical header contents include:

  • Class declarations
  • Class definitions
  • Member function declarations
  • Static data member declarations
  • Inline function definitions
class Radio
{
static int s_count;

int d_size;

public:
int size() const;
};

inline int Radio::size() const
{
return d_size;
}

Avoid file-scope static data and static functions in headers even when they have internal linkage because each translation unit can receive its own copy.

1.1.4 Implementation (.c) Files

Implementation File:
A file containing definitions that implement a component.

Implementation details that should remain local to one translation unit can use internal linkage.

static int fact(int n)
{
return n <= 1 ? 1 : n * fact(n - 1);
}

The book recommends avoiding unnecessary file-scope data and functions with external linkage in implementation files.

Header
└── Interface

Implementation File
└── Implementation

1.2 typedef Declarations

typedef:
Creates an alias for an existing type.

It does not create a new type.

typedef double Inches;
typedef double Pounds;

Because both aliases represent double, they are interchangeable.

Inches height = 180.0;
Pounds weight = height;

Therefore:

typedef does not provide additional type safety.

typedef is useful when:

  • Simplifying complicated type expressions.
  • Naming function-pointer types.
  • Providing implementation-specific type aliases.
typedef int (Person::*Function)(double) const;

1.3 Assert Statements

assert:
Checks that an expression evaluates to nonzero and terminates execution if the condition is false.

assert(pointer);

Assertions act as active comments:

  • They document assumptions.
  • They detect violations during execution.

They are primarily used to detect programming logic errors during development.

When NDEBUG is defined, assertion checks can be removed from production builds.

Therefore, an assertion must not contain code required for normal program behavior.

Bad:

assert(pointer = new char[size]);

Good:

pointer = new char[size];

assert(pointer);

Key Rule:

Normal Program Logic

Must work without assert

assert

Checks assumptions only

1.4 A Few Matters of Style

Coding standards should be:

  • Few
  • Consistent
  • Based on engineering value

Standards affecting interfaces are more important than purely implementation-level style because interfaces directly affect clients.

1.4.1 Identifier Names

Naming Convention:
A consistent method for distinguishing different kinds of identifiers.

The most important rule is consistency.

1.4.1.1 Type Names

Type names begin with an uppercase letter.

class Point;

struct Date;

union Value;

enum Temperature;

typedef int Integer;

Functions and data begin with a lowercase letter.

int getSize();

double temperature;

Types include:

  • Classes
  • Structures
  • Unions
  • Typedefs
  • Enumerations
  • Templates

1.4.1.2 Multi-Word Identifier Names

Two common conventions are:

this_is_a_long_identifier

and

thisIsALongIdentifier

The book uses capitalization to separate words.

getUpperRight()
setSystemScale()

The specific convention is less important than using it consistently.

1.4.1.3 Data Member Names

Instance data members use the d_ prefix.

class Shoe
{
double d_temperature;
int d_size;
};

d_:
Identifies data that represents the state of a particular object.

Static state can use s_.

static int s_count;

s_:
Identifies state that is independent of a particular object instance.

Constants are written in uppercase.

const int DEFAULT_SIZE = 100;
d_
└── Instance State

s_
└── Static State

UPPERCASE
└── Constant

1.4.2 Class Member Layout

The book organizes class member functions into three categories.

Creator:
Creates, copies, or destroys objects.

Car();
Car(const Car&);
~Car();

Manipulator:
A non-const member function that can modify object state.

Car& operator=(const Car&);

void addFuel(double amount);

Accessor:
A const member function that observes an object without modifying its logical state.

double getFuel() const;

double getSpeed() const;

Recommended organization:

class Car
{
public:
// CREATORS

Car();
Car(const Car&);
~Car();

// MANIPULATORS

Car& operator=(const Car&);
void addFuel(double amount);

// ACCESSORS

double getFuel() const;
double getSpeed() const;
};

operator= is classified as a manipulator, not a creator.

CREATORS

Object lifetime

MANIPULATORS

Modify state

ACCESSORS

Observe state

1.5 Iterators

Container:
An object that represents a collection of other objects.

Examples include:

  • Sets
  • Lists
  • Stacks
  • Queues
  • Heaps
  • Hash tables

Iterator:
An object used to sequence through the parts, attributes, or subobjects of another object.

The iterator should normally be separate from the container.

Container

Iterator

Elements

A separate iterator allows:

  • Multiple simultaneous iterations.
  • Iteration state to exist only while needed.
  • Container implementation details to remain hidden from clients.

The iterator is typically closely coupled to the container and may be declared as its friend.

A typical iterator interface in the book is:

class IntSetIter
{
public:
IntSetIter(const IntSet&);

void operator++();

int operator()() const;

operator const void*() const;
};

Usage:

for (IntSetIter it(set); it; ++it)
{
int value = it();
}

Iterator Consistency:
Iterators throughout a system should use a consistent interface and naming convention.

Clients should not assume an iteration order unless the interface explicitly defines one.

1.6 Logical Design Notation

The book uses logical relationships to describe dependencies between logical entities.

Three primary relationships are used:

  • IsA
  • Uses-In-The-Interface
  • Uses-In-The-Implementation

The direction of each relationship points toward the entity on which the other entity depends.

Dependent

Dependency

1.6.1 The IsA Relation

IsA:
Indicates that one class is a kind of another class.

class Message : public String
{
};
Message IsA String

Message depends on String.

The relation corresponds to non-private inheritance.

Meaning:

Derived Class
↓ IsA
Base Class

1.6.2 The Uses-In-The-Interface Relation

Uses-In-The-Interface:
A type is used in a function interface when the type appears in the function declaration.

This includes:

  • Parameter types
  • Return types
void addFuel(Gas*);

Gas is used in the interface of addFuel.

For a class:

A type is used in the public interface of a class if it is used in the interface of one of the class's public member functions.

class IntSetIter
{
public:
IntSetIter(const IntSet&);
};

IntSetIter uses IntSet in its interface.

B Uses-In-The-Interface A

B depends on A

1.6.3 The Uses-In-The-Implementation Relation

Uses-In-The-Implementation:
A type is used in a function implementation when the type is referenced in the function definition.

void Crook::bribe()
{
Judge* bad = 0;
}

Crook uses Judge in its implementation.

For a class, a type is used in its implementation when the type:

  1. Is used by one of its member functions.
  2. Appears in the declaration of a data member.
  3. Is used as a private base class.

Specific forms include:

  • Uses
  • HasA
  • HoldsA
  • WasA

1.6.3.1 Uses

Uses:
A class member function refers to another type.

void Crook::bribe()
{
Judge* judge = 0;
}
Crook Uses Judge

1.6.3.2 HasA and HoldsA

HasA:
A class embeds an object of another type as a data member.

class BattleShip
{
Tower d_controlTower;
};
BattleShip HasA Tower

HoldsA:
A class stores a pointer or reference to another type.

class BattleShip
{
Cannon* d_cannon_p;
Cannon& d_cannon;
};
BattleShip HoldsA Cannon

Both are forms of Uses-In-The-Implementation.

HasA
└── Embedded Object

HoldsA
└── Pointer or Reference

1.6.3.3 WasA

WasA:
A class privately inherits from another type.

class ArizonaMemorial : private BattleShip
{
};

Private inheritance is treated as an implementation detail.

ArizonaMemorial WasA BattleShip

1.7 Inheritance versus Layering

Inheritance:
Represents an IsA relationship between a more specialized type and a more general type.

Derived

Base

Layering:
Building a higher-level abstraction by substantively using lower-level types in its implementation.

Layering often uses:

  • HasA
  • HoldsA
  • Other implementation dependencies
class Person
{
Heart d_heart;
Brain d_brain;
};
Person HasA Heart
Person HasA Brain

A Person is not a Heart.

Therefore inheritance would be inappropriate.

Key Distinction:

Inheritance
└── X IsA Y

Layering
└── X Uses Y

The book emphasizes that layering is much more common than inheritance.

Use inheritance only when the derived type can genuinely be considered a kind of the base type.

1.8 Minimality

Minimality:
Providing sufficient functionality without adding unnecessary features.

Avoid adding functionality merely because a client might eventually want it.

Unnecessary functionality increases:

  • Implementation work
  • Testing work
  • Documentation
  • Maintenance cost
  • Interface complexity

If functionality is not currently required, it can often be deferred.

Necessary Functionality

Implement

Unnecessary Functionality

Defer

Functionality is generally easier to add later than to remove after clients depend on it.

The appropriate degree of minimality depends on the component.

Internal Specialized Component

Minimal interface

Commercial Component Library

More complete and robust interface

1.9 Key Concepts

Multi-File Program

Translation Units

Declaration

Introduces Name

Definition

Defines Entity

Linkage
├── Internal
└── External

Header

Interface

Implementation File

Implementation

typedef

Type Alias

assert

Check Assumptions

Class Layout
├── Creator
├── Manipulator
└── Accessor

Iterator

Traverse Container

Logical Relations
├── IsA
├── Uses-In-The-Interface
└── Uses-In-The-Implementation
├── Uses
├── HasA
├── HoldsA
└── WasA

Logical Hierarchy
├── Inheritance
└── Layering

Minimality

Implement Only Needed Functionality