// In interpolation_impl.hpp
class interpolation_impl
{
public:
};
class linear_interpolation : public interpolation_impl
{
};
class spline_interpolation : public interpolation_impl
{
};
// In interpolation_impl.hpp
class interpolation_impl
{
public:
};
class linear_interpolation : public interpolation_impl
{
};
class spline_interpolation : public interpolation_impl
{
};
// intterpolation_impl.hpp
class interpolation_impl
{
public:
double interpolate(double x) const;
};
// interpolation_impl.cpp
double interpolation_impl::interpolate(double x) const
{
return 0;
}
// interpolation_impl.hpp
class interpolation_impl
{
public:
interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y);
double interpolate(double x) const;
private:
std::vector<double> m_x;
std::vector<double> m_y;
};
class spline_interpolation : public interpolation_impl
{
public:
spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y);
};
// interpolation_impl.cpp
interpolation_impl::interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y)
: m_x(x)
, m_y(y)
{
}
spline_interpolation::spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y)
: interpolation_impl(x, y)
{
}
// interpolation_impl.hpp
class spline_interpolation : public interpolation_impl
{
public:
spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y);
private:
std::vector<double> m_y2;
};
// interpolation_impl.cpp
spline_interpolation::spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y)
: interpolation_impl(x, y)
, m_y2(x.size())
{
spline_derivative(x, y, m_y2); // OK
//spline_derivative(m_x, m_y, m_y2); // Fails
}
Avoid putting data in the protected section, prefer methods
Fix the interpolation_impl and spline_interpolation classes
// interpolation_impl.hpp
class interpolation_impl
{
public:
interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y);
double interpolate(double x) const;
protected:
const std::vector<double>& get_x() const;
const std::vector<double>& get_y() const;
private:
std::vector<double> m_x;
std::vector<double> m_y;
};
// interpolation_impl.cpp
const std::vector<double>& interpolation_impl::get_x() const
{
return m_x;
}
const std::vector<double>& interpolation_impl::get_y() const
{
return m_y;
}
spline_interpolation::spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y)
: interpolation_impl(x, y)
, m_y2(x.size())
{
spline_derivative(get_x(), get_y(), m_y2);
}
// inteprolation_impl.hpp
class spline_interpolation : public interpolation_impl
{
public:
spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y);
double interpolate(double x) const;
private:
std::vector<double> m_y2;
};
// interpolation_impl.cpp
double spline_interpolation::interpolate(double x) const
{
return 1.;
}
// interpolation_impl.hpp
class interpolation_impl
{
public:
interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y);
virtual double interpolate(double x) const;
// as before ...
};
virtual
Add a size_t argument to the overload method in spline_interpolation and run the program again:
// interpolation_impl.hpp
class spline_interpolation : public interpolation_impl
{
public:
spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y);
// Defines a new method interpolate that is not an override of
// interpolate declared in the base class
double interpolate(double x, size_t i) const;
// As before ...
};
// interpolation_impl.cpp
double spline_interpolation::interpolate(double x, size_t i) const
{
return 1.;
}
// interpolation_impl.hpp
class spline_interpolation : public interpolation_impl
{
public:
spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y);
// Error, no interpolate(double, size_t) declared in base class
double interpolate(double x, size_t i) const override;
// Ok, override interpolate(double) declared in base class
double interpolate(double x) const override;
// As before ...
};
override
// interpolation_impl.hpp
class interpolation_impl
{
public:
interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y);
virtual double interpolate(double x) const = 0;
// as before ...
};
An abstract class
// interpolation_impl.hpp
class interpolation_impl
{
public:
interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y);
~interpolation_impl();
// ... as before
};
class spline_interpolation : public interpolation_impl
{
public:
spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y);
~spline_interpolation();
// ... as before
};
// interpolation_impl.cpp
interpolation_impl::interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y)
: m_x(x)
, m_y(y)
{
std::cout << "interpolation_impl constructor" << std::endl;
}
interpolation_impl::~interpolation_impl()
{
std::cout << "interpolation_impl destructor" << std::endl;
}
spline_interpolation::spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y)
: interpolation_impl(x, y)
, m_y2(x.size())
{
spline_derivative(get_x(), get_y(), m_y2);
std::cout << "spline_interpolation constructor" << std::endl;
}
spline_interpolation::~splint_interpolation()
{
std::cout << "spline_interplation destructor" << std::endl;
}
// interpolation_impl.hpp
class interpolation_impl
{
public:
interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y);
virtual ~interpolation_impl();
// ... as before
};
class spline_interpolation : public interpolation_impl
{
public:
spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y);
virtual ~spline_interpolation();
// ... as before
};
// interpolation_impl/hpp
class interpolation_impl
{
public:
virtual void print() const;
// as before ...
};
class spline_interpolation : public interpolation_impl
{
public:
virtual void print() const;
// as before ...
};
// interpolation_impl.cpp
void interpolation_impl::print() const
{
print_vector("m_x", m_x);
print_vector("m_y", m_y);
}
void spline_interpolation::print() const
{
interpolation_impl::print();
print_vector("m_y2", m_y2);
}
class interpolation_impl
{
public:
interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y);
virtual ~interpolation_impl() = default;
interpolation_impl(const interpolation_impl&);
interpolation_impl& operator=(const interpolation_impl&);
interpolation_impl(interpolation_impl&&);
interpolation_impl& operator=(interpolation_impl&&);
};
DON'T.DO.THAT
Prevent users to call copy / move constructor / assign operator
class interpolation_impl
{
public:
interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y);
virtual ~interpolation_impl() = default;
interpolation_impl(const interpolation_impl&) = delete;
interpolation_impl& operator=(const interpolation_impl&) = delete;
interpolation_impl(interpolation_impl&&) = delete;
interpolation_impl& operator=(interpolation_impl&&) = delete;
// as before ...
};
Prevent instantiation of base class
class interpolation_impl
{
public:
virtual ~interpolation_impl() = default;
interpolation_impl(const interpolation_impl&) = delete;
interpolation_impl& operator=(const interpolation_impl&) = delete;
interpolation_impl(interpolation_impl&&) = delete;
interpolation_impl& operator=(interpolation_impl&&) = delete;
// as before ...
protected:
interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y);
};
How to copy an instance of spline_interpolation
class interpolation_impl
{
public:
virtual interpolation_impl* clone() const = 0;
// as before ...
};
class spline_interpolation : public interpolation_impl
{
public:
spline_interpolation* clone() const override;
};
spline_interplation* spline_interpolation::clone() const
{
return new spline_interpolation(get_x(), get_y());
}
spline_interplation* spline_interpolation::clone() const
{
// This actually needs the copy constructor
return new spline_interpolation(*this);
}
class interpolation_impl
{
public:
virtual ~interpolation_impl() = default;
interpolation_impl& operator=(const interpolation_impl&) = delete;
interpolation_impl(interpolation_impl&&) = delete;
interpolation_impl& operator=(interpolation_impl&&) = delete;
virtual interpolation_impl* clone() const = 0;
// as before ...
protected:
interpolation_impl(const std::vector<double>& x,
const std::vector<double>& y);
interpolation_impl(const interpolation_impl&);
// as before ...
};
class spline_interpolation : public interpolation_impl
{
public:
spline_interpolation(const std::vector<double>& x,
const std::vector<double>& y);
virtual ~spline_interpolation();
spline_interpolation* clone() const override;
// as before ...
protected:
spline_interpolation(const spline_interpolation&);
// as before ...
};
interpolation_impl::interpolation_impl(const interpolation_impl& rhs)
: m_x(rhs.m_x)
, m_y(rhs.m_y)
{
}
spline_interpolation::spline_interpolation(const spline_interpolation& rhs)
: interpolation_imp(rhs)
, m_y2(rhs.m_y2)
{
}
spline_interpolation* spline_interpolation::clone() const
{
return new spline_interpolation(*this);
}
That's WRONG
Liskov Substitution Principle (LSP)
If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable property of the propgram
class rectangle : public drawable
{
public:
virtual ~rectangle();
virtual vod draw(canvas&) const;
virtual void resize(size_t length, size_t width);
};
Should square inherit from rectangle?
class square: public rectangle
{
public:
virtual ~square();
virtual vod draw(canvas&) const;
virtual void resize(size_t length, size_t width); // ??
};
square should NOT inherit from rectangle
Non virtual interface
class interpolation
{
public:
double interplate(double x) const;
private:
virtual double interpolate_impl(double x) const = 0;
};
double interpolation::interpolate(double x) const
{
// add code that should be executed before any call to
// interpolate_impl, whatever the inheriting class is.
double res = interpolate_impl(x);
// add code that should be executed after any call to
// interpolate_impl, whatever the inheriting class is.
return res;
}
Template method (pattern)
class algo
{
public:
void run();
private:
virtual void first_step();
virtual void second_step();
virtual void third_step();
};
void algo::run()
{
first_step();
second_step();
third_step();
}
Implement a find_upper_bound method in interpolation_impl that:
class interpolation_impl
{
protected:
size_t find_upper_bound(double x) const;
// as before ...
};
size_t interpolation_impl::find_upper_bound(double x) const
{
if (x < m_x.front() || x > m_x.back())
{
throw std::runtime_error("x out of bounds");
}
auto iter = std::upper_bound(m_x.cbegin(), m_x.cend(), x);
size_t index = static_cast<size_t>( iter - m_x.cbegin());
return index;
}
Implement the interpolate method of spline_interpolation:
double spline_interpolation::interpolate(double x) const
{
size_t upper_bound = find_upper_bound(x);
return spline_interpolate(get_x()[upper_bound-1], get_x()[upper_bound],
get_y()[upper_bound-1], get_y()[upper_bound],
m_y2[upper_bound-1], m_y2[upper_boud],
x);
}
What is the issue with this implementation?
Refactor the implementation to avoid code duplication when adding other interpolation methods:
class interpolation_impl
{
public:
// Not virtual anymore
double interpolate(double x) const;
private:
size_t find_upper_bound(double x) const;
virtual double interpolate_impl(double x, size_t upper_bound) const = 0;
// as before ...
};
class spline_interpolation : public interpolation_impl
{
private:
double interpolate_impl(double x, size_t upper_bound) const override;
// as before ...
};
double interpolation_impl::interpolate(double x) const
{
size_t upper_bound = find_upper_bound(x);
return interpolate_impl(x, upper_bound);
}
double spline_interpolation::interpolate_impl(double x, size_t upper_bound) const
{
return spline_interpolate(get_x()[upper_bound-1], get_x()[upper_bound],
get_y()[upper_bound-1], get_y()[upper_bound],
m_y2[upper_bound-1], m_y2[upper_boud],
x);
}
class my_vector : public std::vector<double>
{
public:
explicit my_vector(const std::string& name);
~my_vector();
private:
std::string m_name;
};
DON'T.DO.THAT.
class my_vector
{
public:
double& operator[](size_t i) { return m_data[i]; }
const double & operator[](size_t i) const { return m_data[i]; }
// etc ...
private:
std::vector<double> m_data;
std::string m_name;
};
class my_vector : private std::vector<double>
{
public:
using base_type = std::vector<doubl>
using base_type::size;
using base_type::empty;
using base_type::operator[];
private:
std::vector<double> m_data;
std::string m_name;
};
If B is a private base of D:
class implementation : public interface1, public interface2
{
public:
virtual ~implementation() = default;
};
class root {};
class interface1 : public root {};
class interface2 : public root {};
// The following is problematic
class implementation : public interface1, public interface2 {}
Add a make_interpolation free function that:
// interpolation_impl.hpp
enum class interpolation_type
{
linear,
spline
};
using interpolation_ptr = std::unique_ptr<interpolation_impl>
interpolation_ptr make_interpolation(const std::vector<double>& x,
const std::vector<double>& y,
interplation_type it);
// interpolation_impl.cpp
interpolation_ptr make_interpolation(const std::vector<double>& x,
const std::vector<double>& y,
interplation_type it)
{
switch(it)
{
case linear:
return std::make_unique<linear_interpolation>(x, y);
clase spline:
return std::make_unique<spline_interpolation>(x, y);
default:
return std::make_unique<linear_interpolation>(x, y);
}
}
Add an interpolation class that:
// interpolation.hpp
class interpolation
{
public:
interpolation(const std::vector<double>& x,
const std::vector<double>& y,
interpolation_type it);
~inteopolation_type() = default;
interpolation(const interpolation&);
interpolation& operator=(const interpolation&);
interpolation(interpolation&&) = default;
interpolation& operator=(interpolation&&) = default;
double interpolate(double x) const;
private:
inteprolation_ptr p_impl;
};
// interpolation.cpp
interpolation::interpolation(const std::vector<double>& x,
const std::vector<double>& y,
interpolation_type it)
: p_impl(make_interpolation(x, y, it);
{
}
interpolation::interpolation(const interpolation& rhs)
: p_impl(rhs.p_impl->clone())
{
}
interpolation& interpolation::operator=(const interpolation& rhs)
{
interpolation_impl tmp = rhs.p_impl->clone();
std::swap(tmp, p_impl);
return *this;
}
double interpolation::interpolate(double x) const
{
return p_impl->interpolate(x);
}
You now have a good understanding of the concepts related to polymorphism and the C++ programming technique called Pimpl.
A colleague comes to you and says that he wants to build a user-friendly interface for his ODE solvers.
At the moment he has only two methods: explicit Euler and Runge-Kutta 2. But he hopes that with your help he will be able to add many more.
double T = 50, t = 0;
std::size_t nite = 20000;
double dt = T/nite;
Lorenz lo(28, 10, 8./3);
std::vector<double> x0{5, -8, 6};
ODE ode(x0, lo, method::euler);
for(std::size_t i = 0; i < nite; ++i)
{
ode.one_step(t, dt);
ode.print_sol();
t += dt;
}
class Lorenz
{
public:
Lorenz(double rho, double sigma, double beta)
: m_rho(rho)
, m_sigma(sigma)
, m_beta(beta)
{}
std::vector<double> operator()(double t, const std::vector<double>& x) const
{
return {
m_sigma*(x[1] - x[0]),
x[0]*(m_rho - x[2]) - x[1],
x[0]*x[1] - m_beta*x[2]
};
}
private:
double m_rho;
double m_sigma;
double m_beta;
};
The Euler method is
$$ y_{n+1} = y_n + \Delta t f(t_n, y_n). $$The Runge-Kutta method of order $2$ is
$$ y_{n+1} = y_n + \Delta t f\left(t_n + \frac{\Delta t}{2}, y_n + \frac{\Delta t}{2}f(t_n, y_n)\right). $$