A library for easier (and faster!) polymorphism in C++.
Avoid memory leaks, enforce correct polymorphic behavior, clone without CRTP, and quickly switch between Base and Derived classes without using dynamic_cast.
Poly is a single-header library. You can download the latest release here.
To use poly, your compiler must be up to ISO C++11 standard.
Poly was verified to work with GCC 4.9.0, clang 3.4, Visual C++ 14 or higher.
This section contains only a basic explanation. For a more complete documentation, refer here.
#include "poly.hpp"
using pl::poly; //For convenience
using pl::factory; //For convenienceWarning: Do not use using namespace pl and using namespace std together in one program. You will get name collisions.
poly is the main class of this library. It's a smart pointer, that understands and supports polymorphic objects.
poly<Animal> p1; //empty
poly<Animal> p2(new Dog); //holds a default-constructed derived object
poly<Animal> p3 = pl::make<poly<Animal>, Dog>(4, "ears", true); //Using a constructor function
poly<Mammal> p4 = pl::transform<poly<Mammal>, Dog>(p3); //Transforming from another polyWhere Dog is derived from Animal.
Animal* animal_ptr = p1.release(); //Release the stored pointer
p1 = new Dog; //Assign a new pointer
if (p1) { //Check if poly holds an object
p1.reset(); //Clear stored value
}bool is_dog = p2.is<Dog>(); //true only if p2 holds exactly a Dog
if (is_dog)
p2.as<Dog>()->pet(); //Cast to dog (only works for Dog)Assume you have the following data structure:
struct Animal;
struct Mammal : virtual Animal;
struct Fish : virtual Animal;
struct Carnivorous : virtual Animal;
struct Dog : Mammal, Carnivorous;
struct Cow : Mammal;This is a fairly complex data structure, but poly.hpp provides tools to down- and sidecast anywhere you need.
poly<Mammal> poly_mamm1(new Dog); //Construct a mammal
poly<Mammal> poly_mamm2(new Cow); //Construct a different mammal
auto poly_carn1 = pl::transform<poly<Carnivorous>, Dog>(poly_mamm1); //Side-cast Mammal to Carnivorous (dogs only!)
auto poly_carn2 = pl::transform<poly<Carnivorous>, Dog>(poly_mamm2); //Compile error (Cow is not a Dog)
auto poly_carn3 = pl::transform<poly<Carnivorous>, Cow>(poly_mamm2); //Compile error (Cow is not Carnivorous)
poly<Dog> poly_doggo = pl::transform<poly<Dog>, Dog>(poly_mammal); //Down-cast mammal to Dog
poly<Fish> poly_fishy = pl::transform<poly<Dog>, Dog>(poly_mammal); //Compile error (doggo is not a fish)poly has an optional second template parameter called CopyDeletePolicy. It defines the behavior of poly when a copy-constructor or a destructor is invoked. poly.hpp contains 2 pre-built policies:
pl::deep(default): Whenpolyis copied, internal object is copied as well.pl::deepinvokes the proper copy-constructor of the derived object. That means you don't need to add theclonemethod to your class.pl::deephas a static check for a virtual destructor in base class to prevent memory leaks.pl::deepadds a memory overhead of one pointer.pl::unique: With this policy,polybehaves like aunique_ptr(a.k.a. is not copy-constructible).pl::uniquealso checks for memory leaks.
pl::uniqueadds no memory overhead.
poly<Animal, pl::deep<Animal>> p1 =
pl::make<poly<Animal, pl::deep<Animal>>, Dog>(); //Same as poly<Animal> p1 = pl::make<poly<Animal>, Dog>()
poly<Animal, pl::unique<Animal>> p2 =
pl::make<poly<Animal, pl::unique<Animal>>, Dog>();
auto p3 = p1; //Works, internal object is also copied
auto p4 = p2; //Error: p2 is not copy-constructibleYou can make your own policies, too. For a type T, a valid policy class provides:
- A default constructor;
- A constructor from
const T* ptr; - A
clonemethod that clones the provided pointer; - A
destroymethod that deletes the pointer.
A basic policy looks like the following:
class my_policy {
my_policy(); //Default constructor
my_policy(const T* ptr); //Construct a policy for operating on the type T
T* clone(const T* ptr); //Clones the object held by ptr.
void destroy(T* ptr); //Destroys the object held by ptr.
};You can also group copy- and delete policies with pl::compound. See the documentation for more details.
Note: if poly is copyable but the derived object is not, on copy a runtime exception will occur.
poly.hpp also contains class factory. factory lets you create poly by passing a string, representing the derived class.
factory<Animal> animal_farm;
//Or factory<Animal, pl::unique<Animal>> for a non-copyable alternative.animal_farm.insert<Dog>();auto doggo = animal_farm.make("Dog"); //Here, auto is poly<Animal, pl::deep<Animal>>Note: Different compilers will require a diffrent string to create a class, depending on what typeid(Dog).name() returns. Consider using some other rtti library along with defining POLY_CUSTOM_TYPE_NAME(type) macro before including for a cross-compiler result.
All described classes are in the namespace pl.
template<class Base, class CopyDeletePolicy = deep<Base>>
class poly;poly is a smart pointer that owns and manages a polymorphic object through a pointer-to-base and disposes of that object when the poly goes out of scope.
The object is disposed of when either of the following happens:
- the managing
polyis destroyed - the managing
polyis assigned another pointer via operator= or reset(). The object is disposed of using the selected policy'sdestroyfunction.
poly may alternatively own no object, in which case it is called empty.
The class satisfies the requirements of MoveConstructible, MoveAssignable, and CopyConstructible, CopyAssignable if the selected policy provides a clone method. The default policy - deep - does provide this method, but can alternatively be switched to unique which does not.
base_type - Base type, from which every polymorphic object is derived. Equivalent to Base.
policy - Policy, used to manage internal pointer's behavior. Equivalent to CopyDeletePolicy.
constexpr poly() noexcept; | (1)
constexpr poly(std::nullptr_t) noexcept; |
poly& operator=(std::nullptr_t) noexcept; |
-------------------------------------------------------------------------
poly(const poly& other); | (2)
poly& operator=(const poly& other); |
-------------------------------------------------------------------------
poly(poly&&) noexcept; | (3)
poly& operator=(poly&&) noexcept; |
-------------------------------------------------------------------------
template <class Base2, class CopyDeletePolicy2, | (4)
class = typename std::enable_if< |
detail::is_stronger_qualified<Base, Base2>::value>::type>> |
poly(const poly<Base2, CopyDeletePolicy2>& other); |
-------------------------------------------------------------------------
template <class Base2, class CopyDeletePolicy2, | (5)
class = typename std::enable_if< |
detail::is_stronger_qualified<Base, Base2>::value>::type>> |
poly(poly<Base2, CopyDeletePolicy2>&& other) noexcept; |
-------------------------------------------------------------------------
template <class Derived> | (6)
explicit poly(Derived* obj); |
template <class Derived> |
poly& operator=(Derived* obj); | - Constructs an empty
polythat owns nothing. Default-constructs the internal policy object. - Copy-constructs
polyfrom anotherpoly. Internal policy is copied, and the internal pointer is cloned using the policy'sclonemethod. - Move-constructs
polyfrom anotherpoly. Internal policy is moved, and the internal pointer simply move as pointer (shallow move). - Copy-constructs
polyfrom a differentpoly. This converting constructor is enabled if new base is more strongly qualified than old base. For example, using this contructor,poly<const Base>is implicitly constructible frompoly<Base>, but notpoly<volatile Base>. - Move-constructs
polyfrom a differentpoly. Same rules as for (4) apply. - Constructs a poly by adopting a raw pointer to a derived class. Besides pointing to a class, derived from Base, the pointer must also not be a polymorhphic pointer to a different object (i.e.
Derived*that points tostruct Derived2 : Derived). Attemping to adopt such a pointer will result in a runtime exception.
~poly();Destructs the managed pointer using selected policy's destroy() function.
template <class T> constexpr bool is() const noexcept;Checks if the stored pointer holds an object is of type T. Returns true if it does, false otherwise.
explicit constexpr operator bool() const noexcept;Checks whether poly owns an object, i.e. whether get() != nullptr. Returns true if it does, false otherwise.
template <class Derived> | (1)
void reset(Derived* ptr); |
-------------------------------------------------------
void reset(std::nullptr_t = nullptr) noexcept; | (2)- Destructs the managed pointer using selected policy's
destroy()function, and replaces it withptr. Forptr, same constraints as in constructor (6) apply. - Destructs the managed pointer using selected policy's
destroy()function, and replaces it withnullptr.
Base* release() noexcept;Releases the ownership of the managed object if any. get() returns nullptr after the call. Returns pointer to the managed object as Base* or nullptr if there was no managed object, i.e. the value which would be returned by get() before the call.
Base& operator*() const; | (1)
-------------------------------------------
Base* operator->() const noexcept; | (2)
Base* get() const noexcept; |- Returns the object owned by
poly, equivalent to*get(). The object must not be empty, otherwise this operation results in undefined behavior. - Returns the managed pointer, or
nullptrif no object is owned.
template <class T>
T* as() const noexcept;Returns a pointer to the object owned by poly in the exact type of that object. Returns nullptr if the stored object is not of type T or if poly is empty.
Note: This function is not the same as dynamic_cast. First, types must match exactly, i.e. no up- or side-casting is allowed. Second, perfomance of this function is much better than of dynamic_cast, as no type tree traversal is performed.
template <class Base, class CopyDeletePolicy = deep<Base>>
class factory;factory is a class that registers and creates poly objects using strings as identifiers.
By default, when a type is registered, a string identifier is generated using std::type_info::name, but this behavior can be overridden by defining a macro POLY_CUSTOM_TYPE_NAME(type) that accepts a type token and retuns a value of type const cher* or std::string. This macro must be defined before including poly.hpp.
Example of a custom name generator using prindex library:
#include "prindex.hpp"
// Using __VA_ARGS__ is recommended to elide the commas that can appear in templated types.
#define POLY_CUSTOM_TYPE_NAME(...) prid<__VA_ARGS__>().name()
#include "poly.hpp"Usage example:
pl::factory<Base> f;
f.insert<Mid1>();
f.insert<Mid2>();
f.insert<Der>();
auto p = f.make("struct Der"); //MSVC
//auto p = f.make("Der"); //GCC or Clang (or prindex)
auto ls = f.list();
for (auto& i : ls) std::cout << i << std::endl;Possible output:
struct Der
struct Mid1
struct Mid2
template <class Derived> void insert();Inserts the type Derived into the factory. This function must be called at least once before poly<Derived> can be made with this instance of the factory.
std::vector<std::string> list() const;Returns a list of all types, registered in the factory, as a std::vector of std::strings that are used to make objects.
poly<Base, CopyDeletePolicy> make(const std::string& name) const;Makes a poly<Base, CopyDeletePolicy>, holding a type, represented by the string name. The required type must be registered using insert before a poly of that type can be made. If no such type is registered, a runtime exception is thrown.
template <class Cloner, class Deleter>
class compound;compound is a helper class that can be used to build policies for poly. When instantiated with a cloner and a deleter, compound becomes a valid CopyDeletePolicy.
Both Cloner and Deleter are classes that
-
Provide either or both:
- a constructor from
const T*, whereTis the type on which the class operates; - a default constructor.
If both constructors are provided, the class will be instantiated with
const T*. - a constructor from
-
An
operator():- For
Cloner,operator()acceptsconst T*and returns a pointer to a copied object; - For
Deleter,operator()acceptsT*, destroys the object, and returns nothing.
- For
All pre-defined policies were made using compound.
template <class Base>
using unique = compound<no_copy,
typename std::conditional<std::has_virtual_destructor<Base>::value,
std::default_delete<Base>,
pmr_delete<Base>>::type>;unique is a policy for poly that disallows copying. When instantiated with this policy, poly is not CopyConstructible nor CopyAssignable.
If the base class does not have a virtual destructor, unique will raise a compiler error on operator() call to prevent memory leaks.
template <class Base>
using deep = compound<deep_copy<Base>,
typename std::conditional<std::has_virtual_destructor<Base>::value,
std::default_delete<Base>,
pmr_delete<Base>>::type>;deep is a policy for poly that allows copying. When instantiated with this policy, poly is CopyConstructible and CopyAssignable.
If the base class does not have a virtual destructor, deep will raise a compiler error on operator() call to prevent memory leaks.
This project is licenced under the MIT licence. It is free for personal and commercial use.