I've been reading the book by Andrei Alexandrescu Modern C++ Design. And i have a question about the decomposition of a class into policies.
Basically what would be a good policy size ? Most examples show parts like construction, destruction, thread safety and so on. Simply put small policies :).
What if i want to create a file io class, that takes a file type as a policy, e.g.
struct XX_Type
{
void AsyncRead(callback c);
void* Write(const uint8* image);
}
struct YY_Type
{
void AsyncRead(callback c);
void* Write(const uint8*, image, uint32 offset);
};
template<class FileType = XX_Type>
class File : public FileType
{
virtual void OnDataRead(const uint8*, uint32 size) = 0;
...
};
Idea is to inherit from file and create a different templates that can later be spawned when needed. Would this be a good fit for policies or should i just pass the file handle to global static functions or should i just create a class for each type of file ? I want to make sure the user errors are low :) and policies seem to be less error prone.
Edit:
Thanks @Claudiordgz for a great indept answer
To give another example would be taking the network approach.
UPD and TCP are very similar and very different at the same time. They both need a socket but one is connectionless the other connection-oriented. But logically they still remain a part of the transport and if i would like to create a higher level of abstraction say a application layer protocol, so taking @Claudiordgz it would at least for me make sense to use the transportlayer as a policy due to its place on the network stack.
Policy-based design. Policy-based design, also known as policy-based class design or policy-based programming, is the term used in Modern C++ Design for a design approach based on an idiom for C++ known as policies.
A policy is a class or class template that defines an interface as a service to other classes. Traits define type interfaces, and policies define function interfaces, so they are closely related. Sometimes, a single class template implements traits and policies.
A policy class is a template parameter used to transmit behavior.
Modern C++ Designis an important book. Fundamentally, it demonstrates 'generic patterns' or 'pattern templates' as a powerful new way of creating extensible designs in C++–a new way to combine templates and patterns that you may never have dreamt was possible, but is.
Policies are a clever switch mechanism. They are used for any of the following:
The idea is to inherit from file and create different templates that can later be spawned when needed. Would this be a good fit for policies, or should I just pass the file handle to global static functions, or should I just create a class for each type of file? I want to make sure the user errors are low :) and policies seem to be less error-prone.
You can go any way you just said, here are the pros/cons of each:
A run method may extend to thousands of lines of code depending on your algorithm.
"Most examples show parts like construction, destruction, thread safety, and so on. Simply put small policies", this is because they are small examples, policies can only extend to your imagination, remember you are the programmer and you take the code to heights no one has ever done. If you don't do it no one will.
And remember Frodo... This task was appointed to you, and if you do not find a way, no one will.
###it would at least for me make sense to use the transport layer as a policy due to its place on the network stack.###
Let's say for example you have a Class named connection. Then you have a Class TCP_conn and Class UDP_conn that each define packets, headers, and methods, for example:
Then you inherit like your example:
Template<class Protocol>
class Connection : public Protocol
{
// your inherited methods would be here
// just define connect or something
}
For example... Let's say you have a Waveform class that generates waveforms
template <class SamplingPolicy >
class Waveform
{
public:
typedef typename SamplingPolicy::iterator iterator;
typedef typename SamplingPolicy::const_iterator const_iterator;
typedef typename SamplingPolicy::inner_iterator inner_iterator;
typedef typename SamplingPolicy::const_inner_iterator const_inner_iterator;
typedef typename SamplingPolicy::size_type size_type;
typedef typename SamplingPolicy::component component;
typedef typename SamplingPolicy::Wave Wave;
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
inner_iterator begin(iterator Itr);
const_inner_iterator begin(iterator Itr) const;
inner_iterator end(iterator Itr);
const_inner_iterator end(iterator Itr) const;
std::size_t Rows() const;
std::size_t Columns() const;
const typename SamplingPolicy::Wave& get() const;
typename SamplingPolicy::Wave& get();
typename SamplingPolicy::component& row(size_type const &n);
const typename SamplingPolicy::component& row(size_type const &n) const;
Waveform();
~Waveform();
template <class Tx, class Ty>
void setup(Tx const &frequency, Ty const &litude);
template <class T>
double Omega(T const &frequency);
Waveform& operator=(Waveform const &rhWave);
Waveform& operator+=(Waveform const &rhWave);
Waveform& operator*=(Waveform const &rhWave);
Waveform& operator-=(Waveform const &rhWave);
Waveform& operator/=(Waveform const &rhWave);
template<class T>
Waveform& operator=(T const &number);
template<class T>
Waveform& operator+=(T const &number);
template<class T>
Waveform& operator*=(T const &number);
template<class T>
Waveform& operator-=(T const &number);
template<class T>
Waveform& operator/=(T const &number);
Waveform operator+(Waveform const &rhWave) const;
Waveform operator-(Waveform const &rhWave) const;
template<class T>
Waveform operator+(T const &number) const;
template<class T>
Waveform operator-(T const &number) const;
Waveform operator/(Waveform const &rhWave) const;
template<class T>
Waveform operator/(T const &number) const;
Waveform operator*(Waveform const &rhWave) const;
template<class T>
Waveform operator*(T const &number) const;
void PrintToConsole(std::size_t columns);
void PrintToFile(std::string const &filename,
std::size_t columns);
protected:
SamplingPolicy _samples;
double _frequency;
double _amplitude;
std::string _frequencyString;
std::string _amplitudeString;
std::map<int,double> _SampleTimes;
private:
bool ValidateSizes(Waveform const &rhWaveform) const;
void print(std::size_t const &columns,
std::ostream &output);
};
But you need policies for 128 Samples and for 1024 Samples, you don't do this on dynamic allocation because you need the sampling rate defined at compilation time rather than on runtime... (in this case for test purposes)
So at 128 points per sample, Policy would look like:
class W_128_Samples
{
public:
static const std::size_t components = 2;
static const std::size_t halfcycle_samples = 64;
typedef boost::array< boost::array<int16_t, halfcycle_samples>, components > Wave;
typedef boost::array<int16_t, halfcycle_samples> component;
typedef boost::array< boost::array<int16_t, halfcycle_samples>, components >::iterator iterator;
typedef boost::array< boost::array<int16_t, halfcycle_samples>, components >::const_iterator const_iterator;
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
typedef int16_t inner_type;
typedef boost::array<int16_t, components>::size_type size_type;
typedef boost::array<int16_t, components>::iterator inner_iterator;
typedef boost::array<int16_t, components>::const_iterator const_inner_iterator;
inner_iterator begin(iterator Itr);
const_inner_iterator begin(iterator Itr) const;
inner_iterator end(iterator Itr);
const_inner_iterator end(iterator Itr) const;
std::size_t Rows() const;
std::size_t Columns() const;
component& row(size_type const &n);
const component& row(size_type const &n) const;
const Wave& get() const;
Wave& get();
protected:
boost::array< boost::array<int16_t, halfcycle_samples>, components > _wave;
};
And at 1024 points per sample, Policy would look like:
class W_1024_Samples
{
public:
static const std::size_t components = 2;
static const std::size_t halfcycle_samples = 512;
typedef boost::array< boost::array<int16_t, halfcycle_samples>, components > Wave;
typedef boost::array<int16_t, halfcycle_samples> component;
typedef boost::array< boost::array<int16_t, halfcycle_samples>, components >::iterator iterator;
typedef boost::array< boost::array<int16_t, halfcycle_samples>, components >::const_iterator const_iterator;
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
typedef int16_t inner_type;
typedef boost::array<int16_t, components>::size_type size_type;
typedef boost::array<int16_t, components>::iterator inner_iterator;
typedef boost::array<int16_t, components>::const_iterator const_inner_iterator;
inner_iterator begin(iterator Itr);
const_inner_iterator begin(iterator Itr) const;
inner_iterator end(iterator Itr);
const_inner_iterator end(iterator Itr) const;
std::size_t Rows() const;
std::size_t Columns() const;
component& row(size_type const &n);
const component& row(size_type const &n) const;
const Wave& get() const;
Wave& get();
protected:
boost::array< boost::array<int16_t, halfcycle_samples>, components > _wave;
};
As you can see, things can get as long as you want.
Another thing is that I implemented my Policies using composition rather than inheritance, which is something that Alexandrescu motivates in his other book of C++ Guidelines with Herb Sutter.
The main thing to take home is:
You need to be saving code -- the less you code, the more value you take home. But this does not mean it will not be hard.
So again, it depends on your problem.
To use my code, for example, I do the following:
Waveform<W_128_Samples> w1;
w1.setup(60, 1000);
Waveform<W_1024_Samples> w2;
w2.setup(60, 1000);
In which 60 is the frequency and 1000 is the amplitude. Both will return arrays, just of different sizes.
The 1024 size will be far smoother when you graph it.
This is really comfortable for me when testing things related to waveforms directly in c++, and I can assemble different waveforms by adding or subtracting.
#What is the advantage?#
Well, the setup method is defined for the Waveform class template. But the array is hold on the policy, as well as the accessors for the array and the size of the array. Plus, you can extend functionality for the policy.
#Why not inheritance?#
Personal Preference and some other reasons based on some books. You can use inheritance as long as you don't go crazy on the base class functionality.
#Why so many methods in the Policy?#
Because the waveform itself is private, a user may create sound, but not alter it.
#Why so much overloading?#
I want to be able to use my Waveforms just like Matlab does, only in C++.
#What do you mean Policy implementation could get big#
Let's say you are implementing Particle Swarm Optimization. You code the PSO in a template that receives an array of data and optimizes your problem based on it.
But your policies are in charge of the Weights for each particle and also for how to pass from N data structure into the array of data.
So you call setup in the Template Manager, as I said the manager receives an Array, but it calls a method of the policy to receive the array. This method in the policy maps a matrix into an array, or a database into an array.
So now you need processing like I/O in the Policy and the Weights.
It could get huge.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With