Specialized Tools and Techniques
C++ provides several less commonly used facilities for specialized low-level and systems programming.
This chapter focuses on:
custom memory allocation
run-time type identification
enumerations
pointers to class members
nested and local classes
unions
nonportable low-level features
19.1 Controlling Memory Allocation
Some programs need more control over how dynamic memory is obtained and released.
C++ allows programs to customize allocation through:
operator new
operator new[]
operator delete
operator delete[]
These functions manage raw storage.
Object construction and destruction are separate steps.
19.1.1 Overloading new and delete
A new expression conceptually performs three steps:
allocate raw storage
↓
construct object
↓
return pointer
Example:
std::string* pointer =
new std::string("hello");
The allocation step calls:
operator new
and the compiler then constructs the std::string.
A delete expression performs the reverse process:
destroy object
↓
release raw storage
delete pointer;
Allocation Functions
Programs can provide their own allocation functions.
Typical forms are:
void* operator new(
std::size_t size);
void operator delete(
void* memory);
Array allocation uses:
operator new[]
operator delete[]
A custom allocator must return suitably aligned raw storage or report allocation failure correctly.
Class-Specific Allocation
A class can define its own allocation functions.
class Widget
{
public:
static void* operator new(
std::size_t size);
static void operator delete(
void* memory);
};
Then:
Widget* pointer =
new Widget;
first looks for a class-specific operator new.
If no suitable class member is found, the global allocation function is used.
Global Allocation Replacement
Defining global versions of:
operator new
operator delete
can affect dynamic allocation throughout the program.
Such replacements must be implemented very carefully because they become part of the program's fundamental memory-management mechanism.
19.1.2 Placement new Expressions
Placement new: Constructs an object in memory that has already been allocated.
Required header:
#include <new>
Example:
void* raw =
::operator new(
sizeof(Widget)
);
Widget* pointer =
new (raw) Widget();
The placement expression:
new (raw) Widget()
does not allocate another block.
It constructs a Widget at the address stored in raw.
Manual Destruction
When object lifetime is managed manually, destruction and storage release are separate.
pointer->~Widget();
::operator delete(raw);
Conceptually:
raw storage
↓ placement new
live object
↓ explicit destructor
raw storage
↓ deallocation
released storage
Placement new is useful in allocators and containers that separate storage allocation from object construction.
19.2 Run-Time Type Identification
Run-Time Type Identification (RTTI): Determines information about the actual type of an object while the program is running.
The two main RTTI facilities are:
dynamic_cast
typeid
RTTI is mainly useful when code operates through base-class pointers or references but needs information about the actual derived type.
When virtual functions can express the required behavior, virtual dispatch is usually preferable.
19.2.1 The dynamic_cast Operator
dynamic_cast performs a checked run-time conversion between related class types.
Common forms are:
dynamic_cast<Derived*>(pointer)
dynamic_cast<Derived&>(reference)
Pointer dynamic_cast
Suppose:
class Base
{
public:
virtual ~Base() = default;
};
class Derived
: public Base
{
};
A checked downcast can be written as:
Base* base =
get_object();
if (Derived* derived =
dynamic_cast<Derived*>(
base))
{
// base actually refers
// to a Derived object
}
If the conversion fails:
result == nullptr
A null source pointer also produces a null result.
Reference dynamic_cast
A reference cannot be null.
Therefore a failed reference cast throws:
std::bad_cast
Example:
try
{
Derived& derived =
dynamic_cast<Derived&>(
base_reference
);
}
catch (
const std::bad_cast& error)
{
// conversion failed
}
Why dynamic_cast Requires Care
Using dynamic_cast means the caller must know which derived type it expects.
Whenever possible:
virtual function
preferred
manual run-time type test
specialized use
19.2.2 The typeid Operator
typeid: Returns information describing the type of an expression or type name.
Required header:
#include <typeinfo>
Example:
if (typeid(object)
== typeid(Derived))
{
// exact type is Derived
}
The result is a reference to:
const std::type_info
Static and Dynamic Type
For a nonpolymorphic expression, typeid reports the expression's static type.
For a polymorphic class expression, typeid can report the object's dynamic type.
Base& reference =
derived_object;
typeid(reference)
can identify the actual derived object when Base is polymorphic.
Dereferencing a Null Pointer
For a polymorphic type:
typeid(*pointer)
may need the dynamic type of the pointed-to object.
If the pointer is null, the operation throws:
std::bad_typeid
19.2.3 Using RTTI
RTTI is useful when an operation depends on the exact dynamic types of two or more objects and cannot naturally be represented as a virtual member function.
A typical pattern is:
if (typeid(lhs)
== typeid(rhs))
{
// same dynamic type
}
or:
if (auto pointer =
dynamic_cast<
const Derived*
>(&object))
{
// Derived-specific work
}
Use RTTI only when the design truly requires explicit type inspection.
19.2.4 The type_info Class
typeid returns an object of type:
std::type_info
Useful operations include:
| Operation | Meaning |
|---|---|
t1 == t2 | Same represented type |
t1 != t2 | Different represented types |
t.name() | Implementation-defined type name |
t.before(u) | Implementation-defined ordering |
Example:
const std::type_info& info =
typeid(object);
std::cout
<< info.name();
The result of name() is implementation dependent and should not be treated as a portable class-name format.
19.3 Enumerations
Enumeration: A distinct type whose values are a fixed set of named constants.
C++11 provides:
unscoped enumeration
scoped enumeration
Unscoped Enumeration
enum Color
{
red,
yellow,
green
};
The enumerator names are introduced into the surrounding scope.
Color color =
red;
Unscoped enumerators can be converted to integral types.
Scoped Enumeration
enum class Color
{
red,
yellow,
green
};
Enumerators must be qualified:
Color color =
Color::red;
Scoped enumerations do not implicitly convert their values to integers.
This gives stronger type safety.
Enumerator Values
Explicit values may be supplied:
enum Status
{
idle = 0,
running = 10,
stopped = 20
};
If an initializer is omitted, the value is one greater than the preceding enumerator.
enum Number
{
zero,
one,
two
};
Conceptually:
zero = 0
one = 1
two = 2
Enumerators are constant expressions.
Underlying Type
An enumeration is represented by an integral type.
C++11 allows that type to be specified explicitly.
enum class Mode
: unsigned char
{
read,
write
};
A scoped enumeration defaults to:
int
when no underlying type is written.
Forward Declaration
An enumeration can be forward declared when its underlying type is known.
Scoped:
enum class Mode;
Unscoped:
enum Token
: unsigned int;
All declarations and the definition must agree about:
scoped or unscoped form
underlying type
19.4 Pointer to Class Member
Pointer to Member: A pointer-like value that identifies a nonstatic member of a class rather than a member inside one particular object.
The pointer records:
class type
+
member type
+
which member
An object is supplied later when the pointer is used.
Static members use ordinary pointers because they do not belong to individual objects.
19.4.1 Pointers to Data Members
Suppose:
class Screen
{
public:
std::string contents;
};
A pointer to the data member can be formed with:
auto data =
&Screen::contents;
Its explicit type is:
std::string Screen::*
Using .*
For an object:
Screen screen;
screen.*data =
"hello";
The operator:
.*
applies a pointer-to-member to an object.
Using ->*
For a pointer to an object:
Screen* pointer =
&screen;
pointer->*data =
"world";
The operator:
->*
applies a pointer-to-member through an object pointer.
Normal class access control still applies when forming pointers to members.
19.4.2 Pointers to Member Functions
A pointer can also identify a nonstatic member function.
class Screen
{
public:
char get() const;
};
Create the pointer:
auto function =
&Screen::get;
Unlike ordinary functions, the address-of operator must be written explicitly for a member-function pointer.
Calling through a Member-Function Pointer
For an object:
Screen screen;
char value =
(screen.*function)();
For an object pointer:
Screen* pointer =
&screen;
char value =
(pointer->*function)();
The parentheses are required because of operator precedence.
Overloaded Member Functions
If the member function is overloaded, its exact type must identify the desired overload.
char (
Screen::*function
)(
Screen::pos,
Screen::pos
) const =
&Screen::get;
The pointer type must include relevant:
return type
parameter types
class type
const qualification
reference qualification
19.4.3 Using Member Functions as Callable Objects
Standard-library adapters can turn member-function pointers into ordinary callable objects.
std::function
std::function<
bool(
const std::string&
)
> empty =
&std::string::empty;
Now:
bool result =
empty(text);
The wrapper supplies the object as an explicit function argument.
std::mem_fn
std::mem_fn deduces the member-pointer type automatically.
auto empty =
std::mem_fn(
&std::string::empty
);
Usage:
empty(text);
empty(&text);
The generated callable can handle either an object or a pointer to an object.
std::bind
A member function can also be adapted with bind.
using namespace
std::placeholders;
auto empty =
std::bind(
&std::string::empty,
_1
);
For direct member-pointer adaptation, mem_fn is often simpler because the compiler deduces the callable interface.
19.5 Nested Classes
Nested Class: A class declared inside another class.
Nested classes are often used for implementation types that logically belong to an enclosing class.
class TextQuery
{
public:
class QueryResult;
};
QueryResult is a type member of TextQuery.
Defining a Nested Class outside Its Enclosing Class
A nested class may be declared inside and defined later.
class TextQuery::QueryResult
{
// ...
};
Until the definition is seen, the nested class is an incomplete type.
Access to the Nested Type
The access section containing the declaration controls who can name the nested type.
public
available to ordinary users
protected
available to derived classes
and friends
private
available only according
to private access rules
Nested Objects Are Independent
An object of the nested class does not automatically contain an object of the enclosing class.
Likewise, an enclosing object does not automatically contain a nested-class object.
Nesting mainly affects:
scope
name lookup
accessibility of the nested type
19.6 union: A Space-Saving Class
union: A class type whose members share the same storage.
Only one member value is normally active at a time.
union Token
{
char character;
int integer;
double floating;
};
Conceptually:
one storage region
character
integer
floating
all overlap
The union occupies enough storage for its largest member, subject to alignment requirements.
Active Member
Token token;
token.integer =
42;
Now the active value is the integer member.
Assigning another member changes which value the program treats as active.
Anonymous Union
An anonymous union has no type or object name.
union
{
char character;
int integer;
};
Its members are accessed directly in the surrounding scope.
An anonymous union cannot define member functions or have private or protected members.
Class-Type Members
C++11 permits unions to contain class-type members such as:
std::string
However, when switching to or from a nontrivial class member, the program must explicitly manage that member's lifetime.
Conceptually:
construct new active member
↓
use it
↓
destroy it before
activating incompatible member
Placement new is commonly used to construct a class-type union member.
Discriminant
A class that manages a union commonly stores a separate value recording which member is active.
This value is called a discriminant.
enum class Kind
{
integer,
floating
};
class Token
{
private:
Kind kind;
union
{
int integer;
double floating;
};
};
The class must keep:
discriminant
and
active union member
synchronized.
19.7 Local Classes
Local Class: A class defined inside a function body.
void function()
{
class Local
{
public:
void run()
{
}
};
}
The class name is visible only within the enclosing scope.
Restrictions on Local Classes
A local class:
- must define its member functions inside the class body
- cannot have static data members
- has only limited access to names from the enclosing function
It may use enclosing:
type names
static variables
enumerators
but not ordinary automatic local variables.
Example
void function()
{
static int shared = 0;
int local = 0;
class Local
{
public:
void run()
{
++shared;
// Error:
// ++local;
}
};
}
Local classes are generally useful only for small implementation details.
19.8 Inherently Nonportable Features
Some C++ facilities depend strongly on:
compiler
machine architecture
ABI
hardware
operating system
Programs using these features may require changes when moved to another platform.
19.8.1 Bit-fields
Bit-field: An integral or enumeration data member stored using a specified number of bits.
using Bit =
unsigned int;
class File
{
Bit mode : 2;
Bit modified : 1;
};
The number after : specifies the field width.
Bit-Field Properties
Bit-fields are often used for:
- compact binary representations
- hardware interfaces
- externally defined binary formats
However, their exact memory layout is implementation dependent.
Prefer Unsigned Types
Signed bit-field behavior is implementation defined.
Therefore unsigned integral types are usually preferred.
No Address of a Bit-Field
The address-of operator cannot be applied to an individual bit-field.
// Error:
// auto pointer = &file.modified;
Therefore an ordinary pointer cannot refer directly to one bit-field.
Bitwise Operations
Multi-bit fields are commonly manipulated with bitwise operators.
mode |= READ;
if (mode & WRITE)
{
// write mode enabled
}
19.8.2 volatile Qualifier
volatile: A type qualifier used when an object's value may change in ways outside ordinary program control.
Typical examples include:
- memory-mapped hardware registers
- values changed by external hardware
- implementation-specific low-level interfaces
volatile int
display_register;
The compiler must treat accesses to volatile objects according to implementation-defined volatile semantics rather than assuming the value changes only through ordinary program statements.
Pointer Forms
As with const, volatility can apply to either the pointed-to object or the pointer itself.
volatile int value;
volatile int* pointer =
&value;
int* volatile
fixed_pointer =
nullptr;
An object may also be both:
const volatile
Volatile Member Functions
Only appropriately volatile-qualified member functions may be called on volatile class objects.
class Device
{
public:
int read() volatile;
};
volatile Is Not Thread Synchronization
volatile does not provide:
atomic operations
mutual exclusion
inter-thread ordering
Use the C++ concurrency facilities, such as atomics or synchronization primitives, for communication between threads.
Copy Control and volatile
Compiler-generated copy and assignment operations usually take nonvolatile references.
Therefore they cannot normally copy directly from volatile objects.
A class that needs such behavior must define appropriate volatile-qualified overloads explicitly.
19.8.3 Linkage Directives: extern "C"
C++ programs sometimes call functions implemented in another programming language, especially C.
A linkage directive tells the compiler that a declaration uses another language's linkage convention.
Single Declaration
extern "C"
std::size_t strlen(
const char*);
The function has C linkage.
Compound Linkage Directive
Several declarations can share the same linkage.
extern "C"
{
void initialize();
void shutdown();
}
Linkage directives must appear at namespace scope rather than inside class or function definitions.
C Header Pattern
When a header must be usable by both C and C++ code, a common pattern is:
#ifdef __cplusplus
extern "C"
{
#endif
void c_function(void);
#ifdef __cplusplus
}
#endif
The C compiler sees an ordinary C declaration.
The C++ compiler applies C linkage.
Portability Requirement
Calling code written in another language depends on compatible:
compilers
calling conventions
object formats
ABIs
extern "C" handles language linkage at the C++ level, but it does not make arbitrary binary interfaces automatically compatible.
Essential Study Checklist
- A
newexpression allocates raw storage and then constructs an object;deletedestroys the object and then releases the storage. operator newandoperator deletemanage raw memory and can be replaced globally or defined for a specific class.- Class-specific allocation functions are searched before global allocation functions for objects of that class.
- Placement new constructs an object in already allocated storage and does not itself obtain a new block.
- Manually managed placement-new objects must be destroyed before their storage is released.
- RTTI consists mainly of
dynamic_castandtypeid, but virtual functions should usually be preferred when they model the operation naturally. - A failed pointer
dynamic_castreturns null, whereas a failed referencedynamic_castthrowsstd::bad_cast. typeidcan report the dynamic type of a polymorphic object and returns aconst std::type_info&.- Scoped enumerations require qualified enumerator names and do not implicitly convert to integers.
- An enum may specify its underlying integral type, and C++11 permits enum forward declarations when the underlying size is known.
- A pointer to member identifies a class member independently of any particular object.
.*applies a pointer-to-member to an object, while->*applies it through an object pointer.- A pointer to member function records the class, return type, parameters, and relevant member-function qualifiers.
std::function,std::mem_fn, andstd::bindcan adapt pointers to member functions into callable objects.- A nested class is a type member of its enclosing class and may be declared inside and defined later with a qualified name.
- A union lets several members share one storage region, with normally one active value at a time.
- Nontrivial class-type union members require explicit lifetime management when the active member changes.
- A discriminant records which union member is currently active.
- A local class is defined inside a function, has local scope, cannot have static data members, and has restricted access to enclosing automatic variables.
- Bit-fields store integral or enumeration members using specified bit widths, but their layout is implementation dependent.
- Unsigned types are normally preferred for bit-fields, and the address of a bit-field cannot be taken.
volatileis intended for implementation-specific externally changing objects such as hardware interfaces; it is not a thread-synchronization mechanism.- Volatile objects require appropriately qualified pointers, references, and member functions.
extern "C"declares C language linkage and is used when C++ code interfaces with compatible C code.- Memory layout, bit-fields, volatile behavior, and foreign-language linkage are platform-sensitive and should be isolated when portability matters.