Advanced C++

Compiling and linking

Máté Cserép

October 2015, Budapest

Overview

  • Header files, include complexity, build time
  • Eliminating unnecessary includes
  • Opaque types, PIMPL, fast PIMPL
  • Binary compatibility issues
  • Template codeblow
  • Using C and C++ together
  • Order of linking

Compile-time dependencies

  • Object-orientation is a well-accepted technology to minimalize object interactions, i.e. run-time dependences between objects.
  • In the same time reducing compile-time dependences is also an important issue for project management purposes.
  • In C++ when anything in a header file changes, all code that includes the header (either directly or indirectly) must be recompiled.
  • Most programmers include much more than necessary.
  • This can seriously degrade build times, especially when a popular header file includes too many other headers.

Eliminate unnecessary includes

#include <iostream>
#include <ostream>
#include <string>
#include <list>
// None of A, B, C, D, E are templates
// Only A and C have virtual functions
#include "a.h"  // class A
#include "b.h"  // class B
#include "c.h"  // class C
#include "d.h"  // class D
#include "e.h"  // class E

class X : public A, private B {
public:
      X(const C&);
  B   f(int, char*);
  C   f(int, C);
  C&  g(B);
  E   h(E);
  virtual std::ostream& print(std::ostream&) const;
private:
  std::string   _name;
  std::list<C>  _clist;
  D             _d;
};

inline std::ostream& operator<<(std::ostream& os, const X& x)
{
  return x.print(os);
}

Questions 

  1. What include directives could be immediately removed without effect?

  2. What further includes could be removed given suitable changes?
    (You may not change the public interface).


Eliminate unnecessary includes

#include <iosfwd>
#include <string>
#include <list>
#include "a.h"  // class A
#include "b.h"  // class B
#include "c.h"  // class C
#include "d.h"  // class D

class E;

class X : public A, private B
{
public:
      X(const C&);
  B   f(int, char*);
  C   f(int, C);
  C&  g(B);
  E   h(E);
  virtual std::ostream& print(std::ostream&) const;
private:
  std::string   _name;
  std::list<C>  _clist;
  D             _d;
};

inline std::ostream& operator<<(std::ostream& os, const X& x)
{
  return x.print(os);
}

Version 2

  • Remove <iostream>. People automatically include <iostream>, even if input functions never used.
  • Replace <ostream> with <iosfwd>. Parameters and return types only need to be forward declared.
  • Replace "e.h" with forward declaration of class E.
  • Cannot remove "a.h", "b.h", "c.h" and "d.h", as we need their full declaration.

The PIMPL idiom

// file x.h
class X {
public:
  X();
  ~X();
  // public members
protected:
  // protected members
private:
  // pointer to forward declared class
  class XImpl *_pimpl;  // opaque pointer
};
  • In C++, when anything in a class definition changes (even private members) all users of that class must be recompiled.
  • To reduce these dependencies, a common technique is to use an opaque pointer to hide some of the implementation details.
  • This is also called the Pointer to IMPLementation (PIMPL) idiom or compilation firewall technique.
  • Using the PIMPL idiom can dramatically reduce code interdependencies and build times, while also provides binary compatibility for the client code.
// file x.cpp
#include "x.h"

// not neccessary to declare as "class"
struct XImpl
{
  /* ... */
};

X::X() : _pimpl(new XImpl()) { }
X::~X() { delete _pimpl; }

The PIMPL idiom

Advantages:

  • Types mentioned only in a class's implementation need no longer be defined for client code, which eliminate extra includes and improve compile speed.
  • A class's implementation can be changed - private members can be added or removed - without replacing client code.

 

Costs:

  • Each construction/destruction must allocate/deallocate memory. (Can be relaxed.)
  • Each access of a hidden member can require at least one extra indirection.
  • Sometimes from the hidden part we must access members in visible part, we need another extra indirection.

Eliminate unnecessary includes

// file x.h
#include <iosfwd>
#include "a.h"  // class A
#include "b.h"  // class B

class C;
class E;

class X : public A, private B
{
public:
      X(const C&);
  B   f(int, char*);
  C   f(int, C);
  C&  g(B);
  E   h(E);
  virtual std::ostream& print(std::ostream&) const;
private:
  // opaque pointer to forward-declared class   
  class XImpl;
  XImpl *pimpl_;
};

inline std::ostream& operator<<(std::ostream& os, 
                                const X& x)
{
  return x.print(os);
}
// file x.cpp
#include <string>
#include <list>
#include "x.h"
#include "c.h"  // class C
#include "d.h"  // class D

// not neccessary to declare as "class"
struct XImpl
{
  std::string   _name;
  std::list<C>  _clist;
  D             _d;
};

Version 3

Usage of PIMPL idiom
(Pointer to IMPLementation) 

The PIMPL idiom

What should be placed into the XImpl object?

  1. Put all private data but not functions into XImpl.
    Not too bad, but there is better.

  2. Put all private members into XImpl.
    Usual practice. Note that virtual member functions cannot be hidden in XImpl, even if they are private. Functions in XImpl may require a "back pointer" (self) to the visible object, which adds another level of indirection. 

  3. Put all private and protected members into XImpl.
    Bad, protected members must be in X.

  4. Make XImpl entirely the class that X would have been, and write X as only the public interface made up entirely of simple forwarding functions.
    Useful in few restricted cases, avoids the back pointer issue. However it makes class X useless for any inheritance.

Replace private inheritance with composition

  • Class B a private base class of X, but  B has no virtual functions.
  • The only major reason one would choose private inheritance over composition is to override virtual functions.
  • Hence, instead of inheriting from B, class X should have a member of type B.
  • To remove the dependency of the "b.h" header, this member should be in X's hidden PIMPL portion.

Guideline

Prefer using PIMPL to insulate client code from implementation details.

Eliminate unnecessary includes

// file x.h
#include <iosfwd>
#include "a.h"  // class A

class B;
class C;
class E;

class X : public A
{
public:
      X(const C&);
  B   f(int, char*);
  C   f(int, C);
  C&  g(B);
  E   h(E);
  virtual std::ostream& print(std::ostream&) const;
private:
  // opaque pointer to forward-declared class   
  struct XImpl;
  XImpl *pimpl_;
};

inline std::ostream& operator<<(std::ostream& os, 
                                const X& x)
{
  return x.print(os);
}
// file x.cpp
#include <string>
#include <list>
#include "x.h"
#include "b.h"  // class B
#include "c.h"  // class C
#include "d.h"  // class D

// not neccessary to declare as "class"
struct XImpl
{
  std::string   _name;
  std::list<C>  _clist;
  B             _b;
  D             _d;
};

Version 4

  • Remove inheritance of B
  • Replace with PIMPL idiom

Fast PIMPL idiom

// file y.h
#include "x.h"

class Y {
public:
  Y();
  /*...*/
private:
  X _x;
};

It's sometimes tempting to cut corners in the name of reducing dependencies or in the name of efficiency, but it may not always be such a good idea. 

// file y.cpp
#include "y.h"

Y::Y() {}

In the code above, the programmer originally has a data member of type X in class Y. This declaration of class Y requires the declaration of class X to be visible (from "x.h"). How can one avoid it?

Fast PIMPL idiom

// file y.h
class X;

class Y {
public:
  Y();
  ~Y();
  /*...*/
private:
  X* _px;
};

Version 2: use the PIMPL idiom.

// file y.cpp
#include "y.h"
#include "x.h"

Y::Y() : _px(new X) { }
Y::~Y() {
  delete _px;
  _px = 0;
}

This nicely hides X, but it turns out that Y is used very widely and the dynamic allocation overhead is degrading performance.
Is their a "perfect" solution that requires neither including x.h in y.h nor the inefficiency of dynamic allocation?

Fast PIMPL idiom

// file y.h
class Y {
public:
  Y();
  ~Y();
  /*...*/
private:
  static const size_t sizeofx = /* value */;
  char _x[sizeofx];
};

Version 3: C++ doesn't support opaque types directly, is there a workaround for that?

// file y.cpp
#include "y.h"
#include "x.h"

Y::Y() {
  // compile-time check
  static_assert(sizeofx >= sizeof(X), 
                "sizeofx too small");
  // does not allocate memory, 
  // but constructs an object at &_x[0]
  new (&_x[0]) X;
}

Y::~Y() {
  (reinterpret_cast<X*>(&_x[0]))->~X();
}
  1. Memory alignment: the char buffer is not guaranteed to be properly aligned
  2. Brittleness: custom assignment operator, destructor is not exception safe, etc.
  3. Maintenance cost when sizeof(X) exceeds sizeofx
  4. Inefficiency when sizeof(X) < sizeofx
  5. Unusual, which is often a synonym for "hack" that should be avoided

Fast PIMPL idiom

// file y.h
class Y {
public:
  Y();
  ~Y();
  /*...*/
private:
  static const size_t sizeofx = /*some value*/;
  static const size_t alignofx = /*correct value*/;

  std::aligned_storage<sizeofx, alignofx>::type _x;
};
// file y.cpp
#include "y.h"
#include "x.h"

Y::Y() {
  // compile-time checks
  static_assert(sizeofx  >= sizeof(X),
                "sizeofx too small");
  static_assert(alignofx == alignof(X),
                "alignofx is incorrect");
  // does not allocate memory,
  // but constructs an object at &_x
  new (&_x) X;
}

Y::~Y() {
  (reinterpret_cast<X*>(&_x))->~X();
}

Version 4: relax the caveat of memory alignment

  • with using union, or even better
  • with using std::aligned_storage (since C++11)

Fast PIMPL idiom

// file y.h
class Y {
public:
  Y();
  ~Y();
  /*...*/
private:
  class YImpl* _pimpl;
};

Version 5: use PIMPL, override the new operator of the YImpl type and implement a custom fixed-size allocator (omitted here).

// file y.cpp
#include "y.h"
#include "x.h"

struct YImpl {
  /* private members omitted */

  void* operator new(size_t)   { /*...*/ }
  void  operator delete(void*) { /*...*/ }
};

Y::Y() : _pimpl(new YImpl) {}
Y::~Y() {
  delete _pimpl;
  _pimpl = 0;
}

This solution has the advantage to fix the caveats of the previous ones on the cost of escalating code complexity with a custom memory allocator. Free space becomes more segmented with multiple free lists for objects.

"Classic" PIMPL vs. Fast PIMPL

When should be used?

  1. Use PIMPL in general.
  2. Use fast PIMPL in particular only after profiling and experience prove that the extra performance boost is really needed.

Template codeblow

#ifndef MATRIX_H
#define MATRIX_H

#include <string>
#include <stdexcept>

template <class T>
class matrix
{
public:
  struct matrixError : public std::logic_error
  {
    matrixError(const std::string& r) : std::logic_error(r) { }
  };
  struct indexError : public matrixError
  {
    indexError(int i, int j) : matrixError("Bad index"), row(i), col(j) { }
    int row;
    int col;
  };

  matrix(int i, int j);
  matrix(const matrix &other);
  ~matrix();
  matrix operator=( const matrix &other);

  int rows() const { return x; }
  int cols() const { return y; }

  T& at(int i, int j) throw(indexError);
  T  at(int i, int j) const throw(indexError);
  T& operator()(int i, int j);
  T  operator()(int i, int j) const;

  matrix operator+=( const matrix &other);
private:
  int  x;
  int  y;
  T   *v;
  void copy(const matrix &other);
  void check(int i, int j) const throw(indexError);
};

#endif

Using templates sometimes cause exponential grow of the code instantiated. This situation is called (template) codeblow.

Template codeblow

#ifndef MATRIX_H
#define MATRIX_H

#include <string>
#include <stdexcept>

class matrixBase
{
public:
  struct matrixError : public std::logic_error
  {
    matrixError(const std::string& r) : std::logic_error(r) { }
  };
  struct indexError : public matrixError
  {
    indexError(int i, int j) : matrixError("Bad index"), row(i), col(j) { }
    int row;
    int col;
  };

  matrixBase(int i, int j) : x(i), y(j) { }

  int rows() const { return x; }
  int cols() const { return y; }
protected:
  int x;
  int y;
  void check(int i, int j) const throw(indexError )
  {
    if ( 0 > i  || i >= x ||  0 > j || j >= y )
      throw indexError(i,j);
  }
};


template <class T>
class matrix : public matrixBase
{
public:
  matrix(int i, int j );
  matrix(const matrix &other);
  ~matrix();
  matrix operator=( const matrix &other);

  T& at( int i, int j) throw(indexError);
  T  at( int i, int j) const throw(indexError);
  T& operator()( int i, int j);
  T  operator()( int i, int j) const;

  matrix operator+=( const matrix &other);
private:
  T   *v;
  void copy( const matrix &other);
};

#endif

Cut off the type-independent part of the class from those dependent from the parameter and use inheritance.

Using C and C++ together

// library.cpp
// compile with g++
#include <iostream>

double multiply(double a)
{
  return a * a;
}
double multiply(double a, double b)
{
  return a * b;
}

extern "C" double testC();
extern "C" double testCPP()
{
  std::cout << multiply(testC()) << std::endl;
  std::cout << multiply(testC(), 2 * testC()) 
            << std::endl;
}
// main.c
// compile with gcc
double testC()
{
  return 10;
}

double testCPP();

int main()
{
  testCPP();
}

C and C++ sources should be compiled separately, but we can link them together. Because of their different strategy to generate external names, we use extern "C".

g++ -c library.cpp -o library.o
gcc -c main.c -o main.o
g++ library.o main.o -o main.exe

Using C and C++ together

extern "C"
{
    int     f(int);
    double  g(double, int);
    // ...
}
#ifdef __cplusplus
extern "C"
{
#endif
    int     f(int);
    double  g(double, int);
    // ...
#ifdef __cplusplus
}
#endif

We can use - and frequently we should use - C and C++ programs together.

C++ header for C files:

C++ header for C/C++ files:

Order of linking

// file: t.h
template <class T>
T t()
{
  return s;
}
// file: main.cpp
#include <iostream>

int g();
int h();

int main()
{
  std::cout << g() << std::endl;
  std::cout << h() << std::endl;
  return 0;
}
// file: a.cpp
static int s = 1;

#include "t.h"

int g()
{
  return t<int>();
}
// file: b.cpp
static int s = 2;

#include "t.h"

int h()
{
  return t<int>();
}

 The order of object files at linking can be significant sometimes

> g++ main.cpp a.cpp b.cpp -o main.exe && main.exe

> g++ main.cpp b.cpp a.cpp -o main.exe && main.exe

1

1

2

2

Made with Slides.com