• Increase font size
  • Default font size
  • Decrease font size

Table of Contents

The progression of chapters in this book roughly follows the standard evolution of an API, from initial design choices, through implementation decisions, versioning, documentation, and testing. The book also includes details on more advanced topics, such as creating scripting and plugin interfaces for your C++ APIs, as well as covering details on how to create static and shared libraries for your API implementation.

Foreword

Preface

Acknowledgments

Introduction

The Introduction chapter defines what an Application Programming Interface (API) is with various examples and describes how an API is represented in C++. This chapter details the difference between API development and standard application development as well as enumerating the pros and cons of APIs in software development. It then illustrates the various layers of APIs that a modern application is built upon, with specific reference to a large open source client/server program. The relationship of the terms API and SDK is explained, as is the relationship between APIs, file formats, and network protocols.

What Are APIs?

Contracts and Contractors

APIs in C++

What’s Different about API Design?

Why Should You Use APIs?

More Robust Code

Code Reuse

Parallel Development

When Should You Avoid APIs?

API Examples

Layers of APIs

A Real-Life Example

File Formats and Network Protocols

About This Book

Qualities

The Qualities chapter enumerates the set of language-neutral qualities that define a good API. This includes providing a good abstraction for the problem being solved and hiding implementation details through a combination of physical and logical hiding techniques. The quality of minimal completeness means that an API should be as small as possible but no smaller. An API should also be easy to use, which includes facets such as being discoverable, difficult to misuse, consistent, orthogonal, platform independent, and making appropriate use of smart pointers such as shared pointers, weak pointers, and scoped pointers. Robust API designs tend to be loosely coupled. Therefore, the term coupling is defined and several techniques are provided to reduce coupling in your APIs. Finally, it is noted that a good API should offer a stable interface that is well documented and regression tested.

Model the Problem Domain

Provide a Good Abstraction

Model the Key Objects

Hide Implementation Details

Physical Hiding: Declaration versus Definition

Logical Hiding: Encapsulation

Hide Member Variables

Hide Implementation Methods

Hide Implementation Classes

Minimally Complete

Don’t Over Promise

Add Virtual Functions Judiciously

Convenience APIs

Easy to Use

Discoverable

Difficult to Misuse

Consistent

Orthogonal

Robust Resource Allocation

Platform Independent

Loosely coupled

Coupling by Name Only

Reducing Class Coupling

Intentional Redundancy

Manager Classes

Callbacks, Observers, and Notifications

Stable, Documented, and Tested

Patterns

Design patterns are general solutions to common software design problems. This chapter provides details for various design patterns and C++ idioms that are particularly relevant to API development. This includes the pimpl idiom, which provides a way to hide all internal details from your public header files. The Singleton pattern provides access to global state. Its implementation in C++ is rather tricky so Singleton best practices are discussed, including initialization and thread safety issues. The Factory Method pattern makes up for several limitations of C++ constructors and can be used to hide implementation details for derived classes. Then various patterns are discussed for wrapping a new API on top of an incompatible or legacy interface. These include the Proxy, Adapter, and Façade patterns. Finally, the Observer pattern is covered as a way to reduce coupling between otherwise unrelated classes.
[Download PDF]

Pimpl Idiom

Using Pimpl

Copy Semantics

Pimpl and Scoped Pointers

Advantages of Pimpl

Disadvantages of Pimpl

Opaque Pointers in C

Singleton

Implementing Singletons in C++

Making Singletons Thread Safe

Singleton versus Dependency Injection

Singleton versus Monostate

Singleton versus Session State

Factory Methods

Abstract Base Classes

Simple Factory Example

Extensible Factory Example

API Wrapping Patterns

The Proxy Pattern

The Adapter Pattern

The Façade Pattern

Observer Pattern

Model–View–Controller

Implementing the Observer Pattern

Push versus Pull Observers

Design

The Design chapter begins with a cautionary tale of how software systems can accrue technical debt and eventually decay without good initial designs that are maintained as the software evolves. Subsequently, functional requirement gathering is discussed, including advice on how best to collect and maintain these requirements. The complementary methods of use case modeling and user story creation are also described. Then the topic of how to approach the design of an API is covered in detail, breaking this process up into high-level architecture design and detailed class design. This proceeds all the way down to the level of function and parameter design, as well as error handling techniques such as error codes and exceptions.

A Case for Good Design

Accruing Technical Debt

Paying Back the Debt

Design for the Long Term

Gathering Functional Requirements

What are Functional Requirements?

Example Functional Requirements

Maintaining the Requirements

Creating Use Cases

Developing Use Cases

Use Case Templates

Writing Good Use Cases

Requirements and Agile Development

Elements of API Design

Architecture Design

Developing an Architecture

Architecture Constraints

Identifying the Major Abstractions

Inventing the Key Objects

Architectural Patterns

Communicating the Architecture

Class Design

Object-Oriented Concepts

Class Design Options

Using Inheritance

Liskov Substitution Principle

The Open/Closed Principle

The Law of Demeter

Class Naming

Function Design

Function Design Options

Function Naming

Function Parameters

Error Handling

Styles

There are several different API styles that can be used to implement any given design. This chapter covers four different programming styles available to C++ developers and describes the pros and cons of each method. These include the use of a flat C API, without any C++ features such as classes, namespaces, or references; object-oriented C++ APIs, which can make use of inheritance, encapsulation, and polymorphism; template-based APIs, which allow functions and data structures to be written in terms of generic types; and data-driven APIs, which map well to web service APIs and support data-driven testing.

Flat C APIs

ANSI C Features

Benefits of an ANSI C API

Writing an API in ANSI C

Calling C Functions from C++

Case Study: FMOD C API

Object-Oriented C++ APIs

Benefits of Object-Oriented API

Case Study: FMOD C++ API

Template-Based APIs

An Example Template-Based API

Templates versus Macros

Advantages of Template-Based APIs

Disadvantages of Template-Based APIs

Data-Driven APIs

Data-Driven Web Services

Advantages of Data-Driven APIs

Disadvantages of Data-Driven APIs

Supporting Variant Argument Lists

Case Study: FMOD Data-Driven API

C++ Usage

This chapter deals with C++ language specific features that can affect API design. This includes using namespaces, either via a consistent prefix for all API symbols or the C++ namespace keyword. The topic of how to declare constructors and assignment operators correctly is then covered, including the rule of “The Big Three.” Const correctness issues are then covered, including method, parameter, and return value const correctness. Template API issues are covered, such as implicit versus explicit instantiation and best practices for structuring template header files. The section on operator overloading describes how to define operators in the most robust fashion and how to decide whether an operator should be a member or free function. The use of default arguments is presented as one way to make backward compatible API changes, though sometimes overloaded functions provide a better solution. The topic of symbol exporting is also explained, in particular the use of __declspec attributes for exposing symbols in a dynamic library on Windows. Finally, the use of good coding conventions is advocated and an example framework for such a document is provided.

Namespaces

Constructors and Assignment

Controlling Compiler-Generated Functions

Defining Constructors and Assignment

The Explicit Keyword

Const Correctness

Method Const Correctness

Parameter Const Correctness

Return Value Const Correctness

Templates

Template Terminology

Implicit Instantiation API Design

Explicit Instantiation API Design

Operator Overloading

Overloadable Operators

Free Operators versus Member Operators

Adding Operators to a Class

Operator Syntax

Conversion Operators

Function Parameters

Pointer versus Reference Parameters

Default Arguments

Avoid #define for Constants

Avoid Using Friends

Exporting Symbols

Coding Conventions

Performance

This chapter covers C++ API features that can impact the performance of your code. In general, you should not warp your API to achieve high performance: always measure performance and target any optimizations at actual bottlenecks. The performance increases of passing arguments as const references and of reducing your #include dependencies are analyzed. It’s shown how naive constant declarations can add weight to your classes, and it’s explained why using initialization lists is a good idea. The chapter also deals with memory optimizations so that your API maps well to modern CPU caching strategies, including variable clustering, bit fields, unions, and size-based types. The use of inlining can improve performance at the cost of exposing internal details, while the technique of copy on write provides a way to define large objects that are efficient to pass around by value. Finally, a survey is presented of performance profiling tools, including time, memory, and multithreading profilers.

Pass Input Arguments by Const Reference

Minimize #include Dependencies

Avoid "Winnebago" Headers

Forward Declarations

Explicit Include Guards

Declaring Constants

The New constexpr Keyword

Initialization Lists

Memory Optimization

Don’t Inline Until You Need To

Copy on Write

Iterating over Elements

Iterators

Random Access

Array References

Performance Analysis

Time-Based Analysis

Memory-Based Analysis

Multithreading Analysis

Versioning

The Versioning chapter deals with the important topic of change management and control. This begins by discussing the relevance of version numbers and introducing an example Version API, to let clients query the current version and feature set for an API. The life cycle of a typical API is then described, including pre-release, release, maintenance, completion, and deprecation. The various terms for backward and forward compatibility are defined, including functional compatibility, source compatibility, and binary compatibility. The important issue of how you can make changes to an API while still maintaining backward compatibility is discussed, including how to make API changes such as adding, changing, deprecating, and removing functionality. Finally, the importance and implementation of API reviews are presented.

Version Numbers

Version Number Significance

Esoteric Numbering Schemes

Creating a Version API

Software Branching Strategies

Branching Strategies

Branching Policies

APIs and Parallel Branches

File Formats and Parallel Products

Life Cycle of an API

Levels of Compatibility

Backward Compatibility

Functional Compatibility

Source Compatibility

Binary Compatibility

Forward Compatibility

How to Maintain Backward Compatibility

Adding Functionality

Changing Functionality

Deprecating Functionality

Removing Functionality

API Reviews

The Purpose of API Reviews

Pre-Release API Reviews

Pre-Commit API Reviews

Documentation

Well-written documentation is a critical element of an API. It describes what an API does and how it behaves, such as defining units for parameters, error conditions, or thrown exceptions. For behavior-only API changes, documentation is the only way to communicate a change to the API. Documentation is also necessary to communicate an interface’s contract when using contract programming. This chapter provides a checklist for what you should document and also details the various types of documentation that you can produce, including automated API documentation, overview documentation, code samples, tutorials, release notes, and license information. A large part of the chapter is dedicated to the effective use of Doxygen as a way to create API documentation automatically.

Reasons to Write Documentation

Defining Behavior

Documenting the Interface’s Contract

Communicating Behavioral Changes

What to Document

Types of Documentation

Automated API Documentation

Overview Documentation

Examples and Tutorials

Release Notes

License Information

Documentation Usability

Using Doxygen

The Configuration File

Comment Style and Commands

API Comments

File Comments

Class Comments

Method Comments

Enum Comments

Sample Header with Documentation

Testing

Automated testing is one of the most important activities you can invest in to ensure that API changes don’t break your clients’ programs. This chapter starts off by presenting the case for writing tests and then categorizes the various types of testing you can perform on an API, including white box testing, black box testing, and gray box testing, as well as unit testing, integration testing, and performance testing. It then covers how to write good tests by detailing the qualities of a good test, what to test, how to focus your testing effort, and how to work effectively with a QA team. The next topic looks at how you can write code that is more amenable to testing. This covers techniques such as test-driven development (TDD), stub objects and mock objects, how to test private code, proper use of assertions, contract programming, and internationalization issues. In the final section, various automated testing tools are presented, such as a range of test harnesses, code coverage tools, bug tracking systems, and continuous build systems.

Reasons to Write Tests

Types of API Testing

Unit Testing

Integration Testing

Performance Testing

Writing Good Tests

Qualities of a Good Test

What to Test

Focusing the Testing Effort

Working with QA

Writing Testable Code

Test-Driven Development

Stub and Mock Objects

Testing Private Code

Using Assertions

Contract Programming

Record and Playback Functionality

Supporting Internationalization

Automated Testing Tools

Test Harnesses

Code Coverage

Bug Tracking

Continuous Build System

Scripting

The Scripting chapter covers the advanced topic of how to expose your C++ API through a scripting language, such as Python, Ruby, Lua, Tcl, or Perl. This begins by explaining why you may want to do this and the issues that you will face in doing so, such as cross-language compatibility issues. The range of script binding technologies is presented, including Boost Python, Simplified Wrapper and Interface Generator (SWIG), Python-SIP, COM Automation, and CORBA. The remainder of the chapter provides practical instruction on how to add Python bindings using Boost Python and Ruby bindings using SWIG. In each case, specifics are covered such as how to expose a class hierarchy, define constructors, support inheritance, use language specific features such as properties in Python, and how to achieve cross-language polymorphism.

Adding Script Bindings

Extending versus Embedding

Advantages of Scripting

Language Compatibility Issues

Crossing the Language Barrier

Script Wrapping Technologies

Boost Python

SWIG

Python-SIP

COM Automation

CORBA

Adding Python Bindings with Boost Python

Building Boost Python

Wrapping a C++ API with Boost Python

Constructors

Extending the Python API

Inheritance in C++

Cross-Language Polymorphism

Supporting Iterators

Putting It All Together

Adding Ruby Bindings with SWIG

Wrapping a C++ API with SWIG

Tuning the Ruby API

Constructors

Extending the Ruby API

Inheritance in C++

Cross-Language Polymorphism

Putting It All Together

Extensibility

Extensibility is an important aspect of API design as it lets users expand the base functionality without requiring you to change the API. The bulk of this chapter deals with how to create a Plugin API, i.e., a way for users to extend the functionality of your API in defined ways by creating dynamic libraries that are discovered and loaded at run time. This deals with how to create stable C++ interfaces and covers the entire architecture, including the plugin API itself, the plugin manager, and example plugins. The chapter also covers other ways to extend an API, such as through inheritance, with a specific example of the Visitor design pattern. It also describes extensibility via templates, where the concept of policy-based templates and the curiously recurring template pattern (CRTP) are presented.

Extending via Plugins

Plugin Model Overview

Plugin System Design Issues

Implementing Plugins in C++

The Plugin API

An Example Plugin

The Plugin Manager

Plugin Versioning

Extending via Inheritance

Adding Functionality

Modifying Functionality

Inheritance and the STL

Inheritance and Enums

The Visitor Pattern

Prohibiting Subclassing

Extending via Templates

Policy-Based Templates

The Curiously Recurring Template Pattern

Appendix A: Libraries

APIs are implemented and distributed as static or dynamic libraries, such as Dynamic Link Libraries (DLLs) on Windows. This chapter describes the difference between static and dynamic libraries, the pros and cons of each, as well as covering the use of dynamic libraries to create plugins. The bulk of the chapter focuses on how to create libraries on Windows, Linux, and Mac OS X. This addresses issues such as exporting symbols, DLL entry points; how to use Microsoft Visual Studio, XCode, and the GNU C++ compiler; using frameworks on the Mac; useful utilities to analyze library files such as libtool, nm, ldd, otool, tasklist.exe, and dlister.exe; and how to write code to load plugins at run time.

Static versus Dynamic Libraries

Static Libraries

Dynamic Libraries

Dynamic Libraries as Plugins

Libraries on Windows

Importing and Exporting Functions

The DLL Entry Point

Creating Libraries on Window

Useful Windows Utilities

Loading Plugins on Windows

Libraries on Linux

Creating Static Libraries on Unix

Creating Dynamic Libraries on Unix

Shared Library Entry Points

Useful Linux Utilities

Loading Plugins on Unix

Finding Dynamic Libraries at Run Time

Libraries on Mac OS X

Creating Static Libraries on Mac OS X

Creating Dynamic Libraries on Mac OS X

Frameworks on Mac OS X

Finding Dynamic Libraries at Run Time

Bibliography

Index

ABOUT THIS SITE

An Application Programming Interface (API) provides a logical interface to a piece of software and hides its internal details. This website is dedicated to a book on designing APIs for C++ and includes articles and links on API development.
 

SOURCE CODE

The book is accompanied by a source code package that contains many of the examples in the text. Download it for free.
 

OTHER BOOKS

Dr. Reddy has also published a computer graphics book called Level of Detail for 3D Graphics. Check it out too!.
 
Copyright (c) 2024 Martin Reddy. All rights reserved. Login