Máté Cserép
October 2015, Budapest
#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
#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
// file x.h
class X {
public:
X();
~X();
// public members
protected:
// protected members
private:
// pointer to forward declared class
class XImpl *_pimpl; // opaque pointer
};
// file x.cpp
#include "x.h"
// not neccessary to declare as "class"
struct XImpl
{
/* ... */
};
X::X() : _pimpl(new XImpl()) { }
X::~X() { delete _pimpl; }
Advantages:
Costs:
// 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)
What should be placed into the XImpl object?
Put all private data but not functions into XImpl.
Not too bad, but there is better.
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.
Put all private and protected members into XImpl.
Bad, protected members must be in X.
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.
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.
// 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
// 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?
// 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?
// 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();
}
// 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
// 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.
When should be used?
#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.
#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.
// 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
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:
// 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