consuming and creating libraries in c++

50
Richard Thomson [email protected] @LegalizeAdulthd github.com/LegalizeAdulthood

Upload: richard-thomson

Post on 21-Jan-2017

537 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Consuming and Creating Libraries in C++

Richard Thomson

[email protected]

@LegalizeAdulthd

github.com/LegalizeAdulthood

Page 2: Consuming and Creating Libraries in C++

Mechanics of Compilation Source

FileHeader

File#include

Object File

Compile

Executable FileLink

Page 3: Consuming and Creating Libraries in C++

Who's Responsible? Header files

Preprocessor

Source files

Compiler

Object files

Linker

Executable files

Run-time loader

Page 4: Consuming and Creating Libraries in C++

Program "The text of the program is kept in units called source files. A source file together with all the headers and source files included via the preprocessor directive #include, less any lines skipped by conditional inclusion preprocessing directives, is called a translation unit."

[2.1 lex.separate]

"A program consists of one or more translation units linked together. A translation unit consists of a sequence of declarations."

[3.5 basic.link]

Page 5: Consuming and Creating Libraries in C++

Declarations and Definitions "A declaration may introduce one or more names into a translation unit or redeclare names introduced by previous declarations. A declaration is a definition unless it declares a function without specifying the function's body, it contains the extern specifier or a linkage specification and neither an initializer nor a function body, it declares a static data member in a class definition, it is a class name declaration, it is an opaque enum declaration, it is a template parameter, it is a parameter declaration in a function declarator that is not a declarator of a function definition, or it is a typedef declaration, an alias declaration, a using declaration, a static assert declaration, an empty declaration, or a using directive." [3.1 basic.def]

Page 6: Consuming and Creating Libraries in C++

(don't panic!)

Page 7: Consuming and Creating Libraries in C++

Examples of Declarations extern int a; // declares a

extern const int c; // declares c

int f(int); // declares f

struct S; // declares S

typedef int Int; // declares Int

extern X anotherX; // declares anotherX

using N::d; // declares d

Page 8: Consuming and Creating Libraries in C++

Examples of Definitions int a; // defines a

extern const int c = 1; // defines c

int f(int x) { return x+a; } // defines f and defines x

struct S { int a; int b; }; // defines S, S::a, and S::b

struct X { // defines X

int x; // defines non-static data member x

static int y; // declares static data member y

X(): x(0) { } // defines a constructor of X

};

int X::y = 1; // defines X::y

enum { up, down }; // defines up and down

namespace N { int d; } // defines N and N::d

namespace N1 = N; // defines N1

X anX; // defines anX

Page 9: Consuming and Creating Libraries in C++

One Definition Rule "No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, or template. [...]

Every program shall contain exactly one definition of every non-inline function or variable that is used in that program. [...]

Exactly one definition of a class is required in a translation unit if the class is used in a way that requires the class to be complete. [...]"

[3.2 basic.def.odr]

Page 10: Consuming and Creating Libraries in C++

One Definition Rule "There can be more than one definition of a class type, enumeration type, inline function with external linkage, class template, non-static function template, static data member of a class template, member function of a class template, or template specialization for which some template parameters are not specified in a program provided that each definition appears in a different translation unit and provided the definitions [are the same]."

[3.2 basic.def.odr]

Page 11: Consuming and Creating Libraries in C++

Linkage "A name is said to have linkage when it might denote the same object, reference, function, type, template, namespace or value as a name introduced in another scope: When a name has external linkage, the entity it denotes

can be referred to by names from scopes of other translation units or from other scopes of the same translation unit.

When a name has internal linkage, the entity it denotes can be referred to by names from other scopes in the same translation unit.

When a name has no linkage, the entity it denotes cannot be referred to by names from other scopes."

[3.5 basic.link]

Page 12: Consuming and Creating Libraries in C++

The Preprocessor Processes preprocessor directives:

#include, #define, #if, etc.

Expands macro invocations with macro bodies Expansion proceeds recursively

The result is a sequence of tokens for the compiler.

Header files act as a massive copy/paste of file contents into your source files. Megabytes of copy/paste...

This is why your builds are slow.

Modules in C++ are the solution, but not in C++17 :-(

Page 13: Consuming and Creating Libraries in C++

The Compiler Invokes the preprocessor on source files for you.

Receives a stream of tokens from the preprocessor.

Performs syntactic and semantic analysis on the token stream to produce a sequence of declarations.

Some of those declarations are definitions.

Definitions allocate space for data or instructions.

The output of compilation is an object file.

The object file encodes the output of compilation: definitions provided by the translation unit.

references to declarations with external linkage.

Page 14: Consuming and Creating Libraries in C++

The Linker Accepts object files from compilation.

Resolves declarations with external linkage.

Produces an executable file for the run-time loader.

Page 15: Consuming and Creating Libraries in C++

The Run-Time Loader Accepts executables from linking.

Establishes a new process for the executable.

Performs any dynamic linking necessary for the executable:

locates dynamic library dependencies

resolves imported symbols by binding them from the dynamic library exporting the symbols

Transfers execution control to the new process.

Page 16: Consuming and Creating Libraries in C++

Symbols and Declarations A symbol is a name used to communicate declarations between the compiler,

the linker and the run-time loader.

Every declaration with external linkage results in a unique symbol.

The process of encoding each C++ identifier into a unique symbol is implementation dependent and is called "name mangling".

Declarations can be overloaded, located within a namespace, const/volatile qualified, etc., all of which can change the mangled name.

The linker and run-time loader deal exclusively with mangled symbols.

Name mangling can be suppressed with C linkage (extern "C"). Interoperability with other languages is achieved through C linkage.

Page 17: Consuming and Creating Libraries in C++

What is a Library? Declares facilities for use by an application.

Defines the implementation for use by an application.

Good libraries define good abstractions.

Page 18: Consuming and Creating Libraries in C++

What is a Header Only Library? Provided as one or more header files.

The declarations and definitions are in the headers.

No linking necessary to use the library.

Just tell your compiler where to find the files.

Often referred to as a compile-time dependency of an executable.

Page 19: Consuming and Creating Libraries in C++

Declarations and Definitions?!? Function definitions are declared inline.

Their definitions are identical for each source file including the header.

Therefore they are allowed by the ODR.

Page 20: Consuming and Creating Libraries in C++

What is a Static Library? An archive of object files.

Produced by the linker/archiver from a set of object files.

May contain indices to accelerate resolution of declarations

with external linkage.

When supplied to the linker, a static library acts as if all of its constituent object files were supplied individually to the linker.

Often referred to as a static dependency of an executable.

Page 21: Consuming and Creating Libraries in C++

Mechanics of Static Libraries Object

FileObject

File

Static Library

Link

Object File

Object File

Page 22: Consuming and Creating Libraries in C++

Dependencies of Static Libraries Source code of a static library can depend on facilities

provided by another static library or another dynamic library.

This creates implicit library dependencies for clients of this static library.

These dependencies accumulate transitively until they are resolved by linking an executable file.

CMake can handle this dependency chain automatically.

Page 23: Consuming and Creating Libraries in C++

Consuming a Static Library Object files provide definitions, but not declarations.

Header files provide declarations, but not definitions.

Consuming a static library consists of:

supplying the header files to the compiler

supplying the library to the linker

Page 24: Consuming and Creating Libraries in C++

Creating a Static Library Create header files containing declarations.

Create source files containing definitions for the declarations.

Compile all source files to be included in the library.

Link the resulting object files into a static library.

Make the headers and the library available to clients.

Page 25: Consuming and Creating Libraries in C++

What is a Dynamic Library? Also called a shared object or shared library.

Instructions and constant data are shared by all running clients.

Produced by the linker from a collection of object files, static libraries and

dynamic libraries.

Encoded as an executable file for use by the run-time loader with: A table of exported names provided by this library. A table of imported names consumed by this library from other dynamic

libraries.

Supplied to the linker as an input to resolve names in an executable and mark them as supplied by the dynamic library.

Some of the mechanics of shared libraries differ between OS platforms.

Page 26: Consuming and Creating Libraries in C++

Mechanics of Dynamic Libraries Static

LibraryDynamic Library

Dynamic Library

Link

Object File

Object File

Page 27: Consuming and Creating Libraries in C++

Dependencies of Dynamic Libraries Source code of a dynamic library can depend on facilities

provided by another static library or another dynamic library.

This creates implicit dynamic library dependencies for clients of this dynamic library.

Static dependencies are resolved when linking the dynamic library.

CMake can handle this dependency chain automatically.

Page 28: Consuming and Creating Libraries in C++

Consuming a Dynamic Library Dynamic library provides definitions to the run-time

loader.

Header files provide declarations for the definitions.

Consuming a dynamic library consists of: supplying the header files to the compiler

supplying the dynamic library to the linker

supplying the dynamic library and its dynamic library dependencies to the run-time loader

Page 29: Consuming and Creating Libraries in C++

Creating a Dynamic Library Create header files containing declarations.

Create source files containing definitions for the declarations.

Compile all source files to be included in the library.

Link the resulting object files into a dynamic library.

Make the headers and the library available to clients.

Page 30: Consuming and Creating Libraries in C++

Principles of Library Design* REP: The Release-Reuse Equivalency Principle

CCP: The Common Closure Principle

CRP: The Common Reuse Principle

ADP: The Acyclic Dependencies Principle

SDP: The Stable Dependencies Principle

SAP: The Stable Abstraction Principle

* From "Agile Principles, Patterns and Practices in C#" by Robert Martin and Micah Martin

Page 31: Consuming and Creating Libraries in C++

REP: The Release-Reuse Equivalency Principle The granule of reuse is the granule of release.

The granule of reuse, a library, can be no smaller than the granule of release. Anything that we reuse must also be released and tracked.

Either all the classes in a library are reusable or none of them are.

Page 32: Consuming and Creating Libraries in C++

CCP: The Common Closure Principle The classes in a library should be closed together

against the same kinds of changes. A change that affects a library affects all the classes in that library and no other libraries.

This is the single responsibility principle (SRP) applied to libraries.

CCP gathers together in one place all the classes that are likely to change for the same reasons.

Page 33: Consuming and Creating Libraries in C++

CRP: The Common Reuse Principle The classes in a library are reused together. If you

reuse one of the classes in a library, you reuse them all.

Classes that are tightly coupled to each other should be in the same library.

Classes that are not tightly bound to each other with class relationships should not be in the same library.

Page 34: Consuming and Creating Libraries in C++

ADP: The Acyclic Dependencies Principle Allow no cycles in the library dependency graph.

Cycles manifest themselves in link command-lines for executables where libraries are repeated as link inputs.

Break cycles by introducing new libraries containing classes on which other libraries depend.

Page 35: Consuming and Creating Libraries in C++

SDP: The Stable Dependencies Principle Depend in the direction of stability.

Changes in dependencies cause testing and integration to propagate up the dependency chain.

Page 36: Consuming and Creating Libraries in C++

SAP: The Stable Abstractions Principle A library should be as abstract as it is stable.

A stable library should be abstract so that its stability does not prevent it from being extended.

An instable library should be concrete, since its instability allows the code within it to be easily changed.

Page 37: Consuming and Creating Libraries in C++

Principles for Header Libraries REP: the granularity of release is the header file.

CCP: the same kind of change should affect all the classes in the header.

CRP: the classes in a header-only library should all be reused together.

ADP: there should be no preprocessor inclusion cycles.

SDP: the header should only depend on more stable headers.

SAP: the more a header is included by other headers and source files, the more abstract it should be.

Page 38: Consuming and Creating Libraries in C++

Principles for Static Libraries REP: the granularity of release is the library and all of its

header files.

CCP: the same kind of change should affect all the classes in the library.

CRP: all the classes in the library are used together.

ADP: there are no link cycles in the library dependency graph.

SDP: the library should depend only on more stable libraries.

SAP: the more stable the library is, the more abstract its header interface should be.

Page 39: Consuming and Creating Libraries in C++

Principles for Dynamic Libraries REP: the granularity of release is the library and all of its

header files.

CCP: the same kind of change should affect all the classes in the library.

CRP: all the classes in the library are used together.

ADP: there are no link cycles in the library dependency graph or in the run-time dependency graph.

SDP: the library should depend only on more stable libraries, either static or dynamic.

SAP: the more stable the library is, the more abstract its header interface should be.

Page 40: Consuming and Creating Libraries in C++

Abstract Interfaces in C++ Template classes/functions use static polymorphism to

express abstract relationships (concepts) between themselves and their template parameters.

Concepts can simplify the syntax and directly express the abstract relationships, but not in C++17 :-(

Clients implement concepts, leaving the implementation free to change without inducing change on the clients.

Page 41: Consuming and Creating Libraries in C++

Abstract Interfaces in C++ Static and dynamic libraries can use dynamic

polymorphism (virtual functions) to express abstract relationships between their classes and their clients.

Publish interfaces as pure virtual base classes implemented by the library classes.

Clients depend on interfaces, leaving the implementation free to change without inducing change in clients.

Page 42: Consuming and Creating Libraries in C++

Thank You!

Page 43: Consuming and Creating Libraries in C++

Bonus Material! Order of initialization of global data

Global data in static dependencies of dynamic libraries

Page 44: Consuming and Creating Libraries in C++

Order of Initialization "Non-local variables with static storage duration are initialized as a consequence of program initiation.

Non-local variables with thread storage duration are initialized as a consequence of thread [initiation]."

[3.6.2 basic.start.init]

Page 45: Consuming and Creating Libraries in C++

Static Initialization "Variables with static storage duration or thread storage duration shall be zero-initialized before any other initialization takes place.

Constant initialization is performed [if the initializing expression is constant]. [...]

Together, zero-initialization and constant initialization are called static-initialization; all other initialization is dynamic initialization.

Static initialization shall be performed before any dynamic initialization takes place."

[3.6.2 basic.start.init]

Page 46: Consuming and Creating Libraries in C++

Dynamic Initialization "Dynamic initialization of a non-local variable with static storage duration is either ordered or unordered. [...]

Variables with ordered initialization defined within a single translation unit shall be initialized in the order of their definitions within a translation unit.

[The] initialization of a variable is indeterminately sequenced with respect to the initialization of a variable defined in a different translation unit.

It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main."

[3.6.2 basic.start.init]

Page 47: Consuming and Creating Libraries in C++

Example of Indeterminate Ordering // g.cpp

extern int f(int);

static int g = f(1);

// is g 2 or 3?

// h.cpp

extern int f(int);

static int h = f(2);

// is h 3 or 4?

// f.cpp

int f(int x) {

static int val = 0;

return ++val + x;

}

Page 48: Consuming and Creating Libraries in C++

Linking Dynamic Libraries Against Static Libraries

Dynamic Library 1

Link

Object File 1

Static Library 1

Dynamic Library 2

Link

Object File 2

Static Library 1

Page 49: Consuming and Creating Libraries in C++

Intentions Subverted Static Library 1 intended that it should have a single copy of its

global data in any program.

Dynamic Library 1 has an instance of Static Library 1's global data.

Dynamic Library 2 has an instance of Static Library 1's global data.

Any executable using both dynamic libraries has two copies of Static Library 1's global data.

Now you have mysterious bugs....

Page 50: Consuming and Creating Libraries in C++

Intentions Upheld Use Static Library 1 as a dynamic library instead.

Use Dynamic Library 1 and Dynamic Library 2 as static libraries instead and static dependencies linked once into executable.

Using dynamic libraries has a tendency to induce the requirement of dynamic libraries elsewhere.

Simplest solution:

all libraries static

all libraries dynamic