Criticism on improved Room class.
Posted: August 9th, 2016, 8:22 pm
I have learned a lot these past few months. I have discovered new semantics and techniques that will allow somewhat more optimised code but importantly makes it safer, easier to read and convenient to use. Looking back, I realise how monstrous the Room class is in terms of safety, readability and convenience and while I can blame it on the use of advanced template meta-programming techniques(when you're using variadic templates with template specialization to provide template recursion termination, you're going to alienate a lot of casual programmers), it mostly comes down to me still experimenting with template meta-programming coding styles and doing it in the worst ways possible.
Now, I have combined all the techniques I learned in object-oriented programming and template meta-programming to write code that looks so much better than before. I have even added comments to help explain certain aspects of the code and make it look less daunting to read by adding actual sentences written for humans. The code now uses placement new() which makes it completely conformed to the standard preventing it from causing undefined behaviour(especially with polymorphic types) and also takes into consideration with memory alignment(though not really necessary even with the use of placement new()). There are also a bunch of other changes that just makes it more accommodating to code with.
Try out the code for yourselves. I have set up examples and test cases for you to run the code on so try to compile those and see if they work for you. Tell me if the code is still alienating and there are still certain aspects that you don't really understand so I could remedy that. If you know certain ways to make the syntax more readable and/or convenient or certain things I could change to optimise it, give me your suggestions. Maybe you like to write code in a certain way and the way the Room class is set up makes it intimidating for you to use. I'll try to find a way to customize it to fit your needs.
Room.h
main.cpp
Now, I have combined all the techniques I learned in object-oriented programming and template meta-programming to write code that looks so much better than before. I have even added comments to help explain certain aspects of the code and make it look less daunting to read by adding actual sentences written for humans. The code now uses placement new() which makes it completely conformed to the standard preventing it from causing undefined behaviour(especially with polymorphic types) and also takes into consideration with memory alignment(though not really necessary even with the use of placement new()). There are also a bunch of other changes that just makes it more accommodating to code with.
Try out the code for yourselves. I have set up examples and test cases for you to run the code on so try to compile those and see if they work for you. Tell me if the code is still alienating and there are still certain aspects that you don't really understand so I could remedy that. If you know certain ways to make the syntax more readable and/or convenient or certain things I could change to optimise it, give me your suggestions. Maybe you like to write code in a certain way and the way the Room class is set up makes it intimidating for you to use. I'll try to find a way to customize it to fit your needs.
Room.h
Code: Select all
#pragma once
#ifndef SHARED_ROOM_H_
#define SHARED_ROOM_H_
#include <cstdint>
#include <memory>
#include <type_traits>
namespace shared
{
/******************************************************************************/
/*!
\brief
Contains types and functions only avaliable to the Room class.
*/
/******************************************************************************/
struct RoomAssets
{
RoomAssets() = delete;
private:
template<class Interface, std::uintmax_t size>
friend class Room;
template<class Interface>
/******************************************************************************/
/*!
\brief
Interface used by the Room class to provide cloning, moving and virtual
destruction semantics for the occupant removing the need for them to implement
it themselves.
*/
/******************************************************************************/
class Agent
{
protected:
//THERE CAN ONLY BE ONE!
Agent() = default;
~Agent() = default;
Agent(const Agent&) = delete;
Agent& operator=(const Agent&) = delete;
public:
/******************************************************************************/
/*!
\brief
Call a generic agent to monitor the room when there is no one there.
\return
An agent.
*/
/******************************************************************************/
static Agent* Call()
{
static Agent agent;
return &agent;
}
/******************************************************************************/
/*!
\brief
Run the destructor of the occupant.
\param some_dude
The poor sucker that actually agreed to stay here.
*/
/******************************************************************************/
virtual void Murder(std::uint8_t*const some_dude) const
{
//Do nothing by default(Used when the room is occupied by nothing).
}
/******************************************************************************/
/*!
\brief
Move the occupant to a new memory location.
\param occupant
The memory containing the occupant to escort.
\param new_location
The new memory location to escort the occupant to.
*/
/******************************************************************************/
virtual void Escort(std::uint8_t*const occupant, std::uint8_t*const new_location) const
{
//Do nothing by default(Used when the room is occupied by nothing).
}
/******************************************************************************/
/*!
\brief
Clone the occupant to a new memory location.
\param occupant
The memory containing the occupant to clone.
\param new_location
The new memory location to clone the occupant to.
*/
/******************************************************************************/
virtual void Clone(const std::uint8_t* occupant, std::uint8_t*const new_location) const
{
//Do nothing by default(Used when the room is occupied by nothing).
}
/******************************************************************************/
/*!
\brief
Visit the occupant and interact with them with an interface(Actually meeting
face to face is soooooo pre-1983!).
\param occupant
The memory containing the occupant to visit.
\return
The interface.
*/
/******************************************************************************/
virtual Interface* Visit(std::uint8_t*const occupant) const
{
//There is no one there lol.
return nullptr;
}
/******************************************************************************/
/*!
\brief
Visit the occupant and interact with them with an interface with const
correctness(Actually meeting face to face is soooooo pre-1983!).
\param occupant
The memory containing the occupant to visit.
\return
The interface.
*/
/******************************************************************************/
virtual const Interface* Visit(const std::uint8_t* occupant) const
{
//There is no one there lol.
return nullptr;
}
};
template<class Interface, class Type>
/******************************************************************************/
/*!
\brief
An efficient killer bred to assassinate with swiftness and discretion.
*/
/******************************************************************************/
class Agent47 : public Agent<Interface>
{
private:
//THERE CAN ONLY BE ONE!
Agent47() = default;
~Agent47() = default;
Agent47(const Agent47&) = delete;
Agent47& operator=(const Agent47&) = delete;
public:
/******************************************************************************/
/*!
\brief
Always there when you need him.
\return
agent 47.
*/
/******************************************************************************/
static Agent47* Call()
{
static Agent47 agent_47;
return &agent_47;
}
/******************************************************************************/
/*!
\brief
Run the destructor of the occupant.
\param some_dude
The poor sucker that actually agreed to stay here.
*/
/******************************************************************************/
void Murder(std::uint8_t*const some_dude) const override
{
reinterpret_cast<Type*>(some_dude)->~Type();
}
/******************************************************************************/
/*!
\brief
Move the occupant to a new memory location.
\param occupant
The memory containing the occupant to escort.
\param new_location
The new memory location to escort the occupant to.
*/
/******************************************************************************/
void Escort(std::uint8_t*const occupant, std::uint8_t*const new_location) const override
{
Type& escort = *reinterpret_cast<Type*>(occupant);
//Move our escort to the new location.
new (new_location) Type(static_cast<Type&&>(escort));
}
/******************************************************************************/
/*!
\brief
Clone the occupant to a new memory location.
\param occupant
The memory containing the occupant to clone.
\param new_location
The new memory location to clone the occupant to.
*/
/******************************************************************************/
void Clone(const std::uint8_t* occupant, std::uint8_t*const new_location) const override
{
//Clone our occupant to the new location. Currently disabled until Visual Studio implements sfinae.
//new (new_location) Type(*reinterpret_cast<const Type*>(occupant));
}
/******************************************************************************/
/*!
\brief
Visit the occupant and interact with them with an interface(Actually meeting
face to face is soooooo pre-1983!).
\param occupant
The memory containing the occupant to visit.
\return
The interface.
*/
/******************************************************************************/
Interface* Visit(std::uint8_t*const occupant) const override
{
return reinterpret_cast<Type*>(occupant);
}
/******************************************************************************/
/*!
\brief
Visit the occupant and interact with them with an interface with const
correctness(Actually meeting face to face is soooooo pre-1983!).
\param occupant
The memory containing the occupant visit.
\return
The interface.
*/
/******************************************************************************/
const Interface* Visit(const std::uint8_t* occupant) const
{
return reinterpret_cast<const Type*>(occupant);
}
};
};
template<class FirstType, class SecondType, bool FirstTypeIsBigger = (sizeof(FirstType) > sizeof(SecondType))>
/******************************************************************************/
/*!
\brief
Used to find the biggest type amoung two types.
*/
/******************************************************************************/
struct GetBiggestType
{
GetBiggestType() = delete;
using Type = FirstType;
};
template<class FirstType, class SecondType>
/******************************************************************************/
/*!
\brief
Recursion termination.
*/
/******************************************************************************/
struct GetBiggestType<FirstType, SecondType, false>
{
GetBiggestType() = delete;
using Type = SecondType;
};
template<class FirstType, class SecondType, class...OtherTypes>
/******************************************************************************/
/*!
\brief
Used to find the biggest type amoung a list of types.
*/
/******************************************************************************/
struct FindBiggestTypeInList
{
FindBiggestTypeInList() = delete;
using Type = typename FindBiggestTypeInList<typename GetBiggestType<FirstType, SecondType>::Type, OtherTypes...>::Type;
};
template<class SecondLastType, class LastType>
/******************************************************************************/
/*!
\brief
Recursion termination.
*/
/******************************************************************************/
struct FindBiggestTypeInList<SecondLastType, LastType>
{
FindBiggestTypeInList() = delete;
using Type = typename GetBiggestType<SecondLastType, LastType>::Type;
};
template<std::uintmax_t size, std::uintmax_t index = 0, bool BiggerOrEqualToSize = (sizeof(void*) * index >= size)>
/******************************************************************************/
/*!
\brief
Get a size that is a multiple of the size of a memory address to make sure
that the memory of that size will be properly aligned(Not nessesary for using
placement new() but it helps).
*/
/******************************************************************************/
struct SetSizeToProperAlignment
{
SetSizeToProperAlignment() = delete;
enum
{
value = SetSizeToProperAlignment<size, index + std::uintmax_t(1)>::value
};
};
template<std::uintmax_t size, std::uintmax_t index>
/******************************************************************************/
/*!
\brief
Recursion termination.
*/
/******************************************************************************/
struct SetSizeToProperAlignment<size, index, true>
{
SetSizeToProperAlignment() = delete;
enum
{
value = sizeof(void*) * index
};
};
template<class FirstType, class SecondType, class...OtherTypes>
using RoomSizeForTypes = SetSizeToProperAlignment<sizeof(typename FindBiggestTypeInList<FirstType, SecondType, OtherTypes...>::Type)>;
template<class Interface, std::uintmax_t size>
/******************************************************************************/
/*!
\brief
A class that contains a block of memory that can be used to represent any type
that can be converted into a specific interface. This enables the ability to
make memory polymorphic and turn it into any type we need at any given time.
The Room remembers what is occupying it and will provide cloning and move
symantics and do automatic virtual destruction on demand without the types nor
the interface needing to implement them. It also helps with alignment since it
allows all these different types to occupy the same array by storing themselves
inside the same room class. The room will accept any type as long as it can be
converted into it's specific interface and is smaller or equal to the size of
the block of memory inside it. The room is set to be occupied by nothing by
default and can be set to this state with the MurderOccupant() function.
*/
/******************************************************************************/
class Room
{
public:
/******************************************************************************/
/*!
\brief
Default Constructor(Will set the room to be occupied by nothing).
*/
/******************************************************************************/
Room()
:
agent_(RoomAssets::Agent<Interface>::Call())
{
}
/******************************************************************************/
/*!
\brief
Destructor.
*/
/******************************************************************************/
~Room()
{
MurderOccupant();
}
/******************************************************************************/
/*!
\brief
Copy constructor. Disabled until Visual Studio implements sfinae.
*/
/******************************************************************************/
Room(const Room& room) = delete;
//:
//agent_(room.agent)
//{
// room.agent_->Clone(room.memory_, memory_);
//}
/******************************************************************************/
/*!
\brief
Copy operator(will destroy the current occupant if occupied). Disabled until
Visual Studio implements sfinae.
\return
this.
*/
/******************************************************************************/
Room& operator=(const Room& room) = delete;
//{
// MurderOccupant();
// agent_ = room.agent_;
// room.agent_->Clone(room.memory_, memory_);
// return *this;
//}
/******************************************************************************/
/*!
\brief
Move constructor.
*/
/******************************************************************************/
Room(Room&& room)
:
agent_(room.agent_)
{
agent_->Escort(room.memory_, memory_);
}
/******************************************************************************/
/*!
\brief
Move operator(will destroy the current occupant if occupied).
\return
this.
*/
/******************************************************************************/
Room& operator=(Room&& room)
{
//To prevent stealing of own resources(Possible because object has been created already.)
if (this == &room)
{
return *this;
}
MurderOccupant();
agent_ = room.agent_;
agent_->Escort(room.memory_, memory_);
return *this;
}
template<class Type>
/******************************************************************************/
/*!
\brief
Copy constructor for types convertable to the interface.
*/
/******************************************************************************/
Room(const Type& type)
{
static_assert(sizeof(type) <= size, "That type is bigger than the size of the room's memory block!");
static_assert(std::is_convertible<Type, Interface>::value, "That type cannot be converted to the interface!");
//Use placement new to define our block of memory.
new (memory_) Type(type);
//Get our top agent on watch right now!
agent_ = RoomAssets::Agent47<Interface, Type>::Call();
}
template<class Type>
/******************************************************************************/
/*!
\brief
Copy operator for types convertable to the interface(will destroy the current
occupant if occupied).
\return
this.
*/
/******************************************************************************/
Room& operator=(const Type& type)
{
static_assert(sizeof(type) <= size, "That type is bigger than the size of the room's memory block!");
static_assert(std::is_convertible<Type, Interface>::value, "That type cannot be converted to the interface!");
//murder the previous occupant to make room for the new one.
MurderOccupant();
//Use placement new to define our block of memory.
new (memory_) Type(type);
//Get our top agent on watch right now!
agent_ = RoomAssets::Agent47<Interface, Type>::Call();
return *this;
}
template<class Type>
/******************************************************************************/
/*!
\brief
Move constructor for types convertable to the interface.
*/
/******************************************************************************/
Room(Type&& type)
{
static_assert(sizeof(type) <= size, "That type is bigger than the size of the room's memory block!");
static_assert(std::is_convertible<Type, Interface>::value, "That type cannot be converted to the interface!");
//Use placement new to define our block of memory.
new (memory_) Type(std::move(type));
//Get our top agent on watch right now!
agent_ = RoomAssets::Agent47<Interface, Type>::Call();
}
template<class Type>
/******************************************************************************/
/*!
\brief
Move operator for types convertable to the interface(will destroy the current
occupant if occupied).
\return
this.
*/
/******************************************************************************/
Room& operator=(Type&& type)
{
static_assert(sizeof(type) <= size, "That type is bigger than the size of the room's memory block!");
static_assert(std::is_convertible<Type, Interface>::value, "That type cannot be converted to the interface!");
//murder the previous occupant to make room for the new one.
MurderOccupant();
//Use placement new to define our block of memory.
new (memory_) Type(std::move(type));
//Get our top agent on watch right now!
agent_ = RoomAssets::Agent47<Interface, Type>::Call();
return *this;
}
/******************************************************************************/
/*!
\brief
Member access operator overload to interact with the occupant with the
interface(If the room is not occupied by anything, it will return a nullptr
causing an error).
\return
The interface.
*/
/******************************************************************************/
Interface* operator->()
{
return agent_->Visit(memory_);
}
/******************************************************************************/
/*!
\brief
Member access operator overload to interact with the occupant with the
interface with const correcness.(If the room is not occupied by anything, it
will return a nullptr causing an error).
\return
The interface.
*/
/******************************************************************************/
const Interface* operator->() const
{
return agent_->Visit(memory_);
}
/******************************************************************************/
/*!
\brief
Return an Interface pointer to the occupying type(will return null if not
occupied by anything).
\return
The interface.
*/
/******************************************************************************/
Interface* Visit()
{
return agent_->Visit(memory_);
}
/******************************************************************************/
/*!
\brief
Member access operator overload to interact with the occupant with the
interface with const correcness.
\return
The interface.
*/
/******************************************************************************/
const Interface* Visit() const
{
return agent_->Visit(memory_);
}
/******************************************************************************/
/*!
\brief
Murder the occupant to make room for other occupants or just for funsies(will
set the room to be occupied by nothing).
*/
/******************************************************************************/
void MurderOccupant()
{
agent_->Murder(memory_);
agent_ = RoomAssets::Agent<Interface>::Call();
}
private:
std::uint8_t memory_[size];
RoomAssets::Agent<Interface>* agent_;
};
}
#endif
Code: Select all
#include <iostream>
#include <vector>
#include "TypeList.h"
#include "Room.h"
namespace shape_library
{
class Square{};
class Triangle{};
class Circle{};
}
namespace mesh_library
{
class Mesh{};
}
class Collidable
{
public:
using Square = shape_library::Square;
using Triangle = shape_library::Triangle;
using Circle = shape_library::Circle;
using Mesh = mesh_library::Mesh;
virtual void CollideWith(Collidable* collidable) = 0;
virtual void CollideWith(Square& collidable) = 0;
virtual void CollideWith(Triangle& collidable) = 0;
virtual void CollideWith(Circle& collidable) = 0;
virtual void CollideWith(Mesh& collidable) = 0;
};
template<class Type>
class CollidableTemplate : public Collidable
{
public:
//unsupported type
CollidableTemplate() = delete;
};
template<>
class CollidableTemplate<Collidable::Square> : public Collidable
{
public:
CollidableTemplate(Square& collidable)
:
collidable_(collidable)
{
}
~CollidableTemplate()
{
std::cout << "Square collider has terminated" << std::endl;
}
void CollideWith(Collidable* collidable) override
{
collidable->CollideWith(collidable_);
}
void CollideWith(Square& collidable) override
{
std::cout << "Square has collided with square!" << std::endl;
}
void CollideWith(Triangle& collidable) override
{
std::cout << "Square has collided with triangle!" << std::endl;
}
void CollideWith(Circle& collidable) override
{
std::cout << "Square has collided with circle!" << std::endl;
}
void CollideWith(Mesh& collidable) override
{
std::cout << "Square has collided with mesh!" << std::endl;
}
private:
Square& collidable_;
};
template<>
class CollidableTemplate<Collidable::Triangle> : public Collidable
{
public:
CollidableTemplate(Triangle& collidable)
:
collidable_(collidable)
{
}
~CollidableTemplate()
{
std::cout << "Triangle collider has terminated" << std::endl;
}
void CollideWith(Collidable* collidable) override
{
collidable->CollideWith(collidable_);
}
void CollideWith(Square& collidable) override
{
std::cout << "Triangle has collided with square!" << std::endl;
}
void CollideWith(Triangle& collidable) override
{
std::cout << "Triangle has collided with triangle!" << std::endl;
}
void CollideWith(Circle& collidable) override
{
std::cout << "Triangle has collided with circle!" << std::endl;
}
void CollideWith(Mesh& collidable) override
{
std::cout << "Triangle has collided with mesh!" << std::endl;
}
private:
Triangle& collidable_;
};
template<>
class CollidableTemplate<Collidable::Circle> : public Collidable
{
public:
CollidableTemplate(Circle& collidable)
:
collidable_(collidable)
{
}
~CollidableTemplate()
{
std::cout << "Circle collider has terminated" << std::endl;
}
void CollideWith(Collidable* collidable) override
{
collidable->CollideWith(collidable_);
}
void CollideWith(Square& collidable) override
{
std::cout << "Circle has collided with square!" << std::endl;
}
void CollideWith(Triangle& collidable) override
{
std::cout << "Circle has collided with triangle!" << std::endl;
}
void CollideWith(Circle& collidable) override
{
std::cout << "Circle has collided with circle!" << std::endl;
}
void CollideWith(Mesh& collidable) override
{
std::cout << "Circle has collided with mesh!" << std::endl;
}
private:
Circle& collidable_;
};
template<>
class CollidableTemplate<Collidable::Mesh> : public Collidable
{
public:
CollidableTemplate(Mesh& collidable)
:
collidable_(collidable)
{
}
~CollidableTemplate()
{
std::cout << "Mesh collider has terminated" << std::endl;
}
void CollideWith(Collidable* collidable) override
{
collidable->CollideWith(collidable_);
}
void CollideWith(Square& collidable) override
{
std::cout << "Mesh has collided with square!" << std::endl;
}
void CollideWith(Triangle& collidable) override
{
std::cout << "Mesh has collided with triangle!" << std::endl;
}
void CollideWith(Circle& collidable) override
{
std::cout << "Mesh has collided with circle!" << std::endl;
}
void CollideWith(Mesh& collidable) override
{
std::cout << "Mesh has collided with mesh!" << std::endl;
}
private:
Mesh& collidable_;
};
template<class Type>
CollidableTemplate<Type> GetCollidable(Type& type)
{
return CollidableTemplate<Type>(type);
}
void main()
{
enum
{
room_size = shared::RoomSizeForTypes<CollidableTemplate<shape_library::Square>, CollidableTemplate<shape_library::Triangle>, CollidableTemplate<shape_library::Circle>, CollidableTemplate<mesh_library::Mesh>>::value
};
using RoomForCollidables = shared::Room<Collidable, room_size>;
std::vector<RoomForCollidables> collidables(1);
std::vector<Collidable::Square> squares(15);
std::vector<Collidable::Circle> circles(15);
std::vector<Collidable::Triangle> triangles(15);
std::vector<Collidable::Mesh> meshes(15);
//test for setting the room. The temp collidable we copied from should run it's destructor.
collidables[0] = GetCollidable(circles[1]);
//should override CollidableTemplate<Circle> and call it's destructor. The temp collidable we copied from should run it's destructor last.
collidables[0] = GetCollidable(circles[1]);
//same deal.
collidables[0] = GetCollidable(circles[1]);
collidables[0] = GetCollidable(squares[1]);
//Override CollidableTemplate<Square> and set the room to nothing.
collidables[0] = RoomForCollidables();
std::cout << std::endl;
//test for Room's compatibility with vector.
for (std::size_t index = 0; index < 15; ++index)
{
collidables.push_back(RoomForCollidables());
}
//There are now 16 instances of a room of collidables(1 from the beginning and 15 from the for loop).
collidables[0] = GetCollidable(circles[1]);
collidables[1] = GetCollidable(squares[1]);
collidables[2] = GetCollidable(meshes[1]);
collidables[3] = GetCollidable(circles[1]);
collidables[4] = GetCollidable(meshes[1]);
collidables[5] = GetCollidable(meshes[1]);
collidables[6] = GetCollidable(triangles[1]);
collidables[7] = GetCollidable(meshes[1]);
collidables[8] = GetCollidable(meshes[1]);
collidables[9] = GetCollidable(triangles[1]);
collidables[10] = GetCollidable(triangles[1]);
collidables[11] = GetCollidable(meshes[1]);
collidables[12] = GetCollidable(triangles[1]);
collidables[13] = GetCollidable(triangles[1]);
collidables[14] = GetCollidable(triangles[1]);
collidables[15] = GetCollidable(squares[1]);
std::cout << std::endl;
//Should print collidables[second index] has collided with collidables[first index]
collidables[0]->CollideWith(collidables[8].Visit());
collidables[11]->CollideWith(collidables[3].Visit());
collidables[3]->CollideWith(collidables[6].Visit());
collidables[1]->CollideWith(collidables[11].Visit());
collidables[2]->CollideWith(collidables[7].Visit());
collidables[1]->CollideWith(collidables[6].Visit());
collidables[2]->CollideWith(collidables[15].Visit());
collidables[11]->CollideWith(collidables[10].Visit());
collidables[0]->CollideWith(collidables[5].Visit());
collidables[0]->CollideWith(collidables[3].Visit());
collidables[0]->CollideWith(collidables[4].Visit());
collidables[5]->CollideWith(collidables[3].Visit());
collidables[5]->CollideWith(collidables[7].Visit());
collidables[14]->CollideWith(collidables[7].Visit());
collidables[7]->CollideWith(collidables[13].Visit());
collidables[7]->CollideWith(collidables[2].Visit());
std::cout << std::endl;
//Test for compatibility with erase.
collidables.erase(collidables.begin());
std::cout << std::endl;
//same but index + 1
collidables[0]->CollideWith(collidables[12].Visit());
collidables[0]->CollideWith(collidables[13].Visit());
collidables[10]->CollideWith(collidables[7].Visit());
collidables[0]->CollideWith(collidables[0].Visit());
collidables[13]->CollideWith(collidables[1].Visit());
collidables[14]->CollideWith(collidables[11].Visit());
collidables[2]->CollideWith(collidables[13].Visit());
collidables[14]->CollideWith(collidables[8].Visit());
collidables[12]->CollideWith(collidables[11].Visit());
collidables[3]->CollideWith(collidables[11].Visit());
collidables[3]->CollideWith(collidables[11].Visit());
collidables[6]->CollideWith(collidables[10].Visit());
collidables[12]->CollideWith(collidables[4].Visit());
collidables[14]->CollideWith(collidables[5].Visit());
collidables[7]->CollideWith(collidables[8].Visit());
collidables[12]->CollideWith(collidables[2].Visit());
std::cout << std::endl;
collidables.clear();
std::cout << std::endl;
system("pause");
}