An open API service indexing awesome lists of open source software.

https://github.com/bindreams/poly

A library for easier polymorphism in C++.
https://github.com/bindreams/poly

Last synced: about 1 year ago
JSON representation

A library for easier polymorphism in C++.

Awesome Lists containing this project

README

          

# poly
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`.
## Installation
Poly is a single-header library. You can download the latest release [here](https://github.com/andreasxp/poly/releases).
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.
## Usage
This section contains only a basic explanation. For a more complete documentation, refer [here](#documentation).
#### Include header:
```c++
#include "poly.hpp"
using pl::poly; //For convenience
using pl::factory; //For convenience
```
**Warning:** Do not use `using namespace pl` and `using namespace std` together in one program. You will get name collisions.

### `poly`
`poly` is the main class of this library. It's a smart pointer, that understands and supports polymorphic objects.

#### Create a `poly`:
```c++
poly p1; //empty
poly p2(new Dog); //holds a default-constructed derived object
poly p3 = pl::make, Dog>(4, "ears", true); //Using a constructor function
poly p4 = pl::transform, Dog>(p3); //Transforming from another poly
```
Where `Dog` is derived from `Animal`.

#### Use `poly`:
```c++
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
}
```

```c++
bool is_dog = p2.is(); //true only if p2 holds exactly a Dog
if (is_dog)
p2.as()->pet(); //Cast to dog (only works for Dog)
```

#### Create a `poly`, vol. 2:
Assume you have the following data structure:
```c++
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.
```c++
poly poly_mamm1(new Dog); //Construct a mammal
poly poly_mamm2(new Cow); //Construct a different mammal

auto poly_carn1 = pl::transform, Dog>(poly_mamm1); //Side-cast Mammal to Carnivorous (dogs only!)
auto poly_carn2 = pl::transform, Dog>(poly_mamm2); //Compile error (Cow is not a Dog)
auto poly_carn3 = pl::transform, Cow>(poly_mamm2); //Compile error (Cow is not Carnivorous)

poly poly_doggo = pl::transform, Dog>(poly_mammal); //Down-cast mammal to Dog
poly poly_fishy = pl::transform, Dog>(poly_mammal); //Compile error (doggo is not a fish)
```

#### Using policies to customize the behavior of `poly`
`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): When `poly` is copied, internal object is copied as well. `pl::deep` invokes the proper copy-constructor of the derived object. That means you don't need to add the `clone` method to your class. `pl::deep` has a static check for a virtual destructor in base class to prevent memory leaks.
`pl::deep` adds a memory overhead of one pointer.
* `pl::unique`: With this policy, `poly` behaves like a `unique_ptr` (a.k.a. is not copy-constructible). `pl::unique` also checks for memory leaks.
`pl::unique` adds no memory overhead.

```c++
poly> p1 =
pl::make>, Dog>(); //Same as poly p1 = pl::make, Dog>()
poly> p2 =
pl::make>, Dog>();

auto p3 = p1; //Works, internal object is also copied
auto p4 = p2; //Error: p2 is not copy-constructible
```
You can make your own policies, too. For a type `T`, a valid policy class provides:
1. A default constructor;
2. A constructor from `const T* ptr`;
3. A `clone` method that clones the provided pointer;
4. A `destroy` method that deletes the pointer.

A basic policy looks like the following:
```c++
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](#class-compound) for more details.

*Note: if `poly` is copyable but the derived object is not, on copy a runtime exception will occur.*

### `factory`
`poly.hpp` also contains class `factory`. `factory` lets you create poly by passing a string, representing the derived class.
#### Create a `factory`:
```c++
factory animal_farm;
//Or factory> for a non-copyable alternative.
```

#### Register a class in the factory:
```c++
animal_farm.insert();
```

#### Make a dog:
```c++
auto doggo = animal_farm.make("Dog"); //Here, auto is poly>
```
*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](https://github.com/andreasxp/prindex) along with defining POLY_CUSTOM_TYPE_NAME(type) macro before including for a cross-compiler result.*

## Documentation
All described classes are in the namespace `pl`.
### `class poly`
```c++
template>
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 `poly` is destroyed
* the managing `poly` is assigned another pointer via operator= or reset().
The object is disposed of using the selected policy's `destroy` function.

`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.

#### Member types
`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`.

#### Member functions
##### Constructors and assignment operators
```c++
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 ::value>::type>> |
poly(const poly& other); |
-------------------------------------------------------------------------
template ::value>::type>> |
poly(poly&& other) noexcept; |
-------------------------------------------------------------------------
template | (6)
explicit poly(Derived* obj); |
template |
poly& operator=(Derived* obj); |
```
1. Constructs an empty `poly` that owns nothing. Default-constructs the internal policy object.
2. Copy-constructs `poly` from another `poly`. Internal policy is copied, and the internal pointer is *cloned* using the policy's `clone` method.
3. Move-constructs `poly` from another `poly`. Internal policy is moved, and the internal pointer simply move as pointer (shallow move).
4. Copy-constructs `poly` from a different `poly`. This converting constructor is enabled if new base is more strongly qualified than old base. For example, using this contructor, `poly` is implicitly constructible from `poly`, but not `poly`.
5. Move-constructs `poly` from a different `poly`. Same rules as for (4) apply.
6. 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 to `struct Derived2 : Derived`). Attemping to adopt such a pointer will result in a runtime exception.
##### Destructor
```c++
~poly();
```
Destructs the managed pointer using selected policy's `destroy()` function.
##### Observers
```c++
template constexpr bool is() const noexcept;
```
Checks if the stored pointer holds an object is of type `T`. Returns `true` if it does, `false` otherwise.
```c++
explicit constexpr operator bool() const noexcept;
```
Checks whether `poly` owns an object, i.e. whether `get() != nullptr`. Returns `true` if it does, `false` otherwise.
##### Modifiers
```c++
template | (1)
void reset(Derived* ptr); |
-------------------------------------------------------
void reset(std::nullptr_t = nullptr) noexcept; | (2)
```
1. Destructs the managed pointer using selected policy's `destroy()` function, and replaces it with `ptr`. For `ptr`, same constraints as in constructor (6) apply.
2. Destructs the managed pointer using selected policy's `destroy()` function, and replaces it with `nullptr`.
```c++
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.
##### Member access
```c++
Base& operator*() const; | (1)
-------------------------------------------
Base* operator->() const noexcept; | (2)
Base* get() const noexcept; |
```
1. Returns the object owned by `poly`, equivalent to `*get()`. The object must not be empty, otherwise this operation results in undefined behavior.
2. Returns the managed pointer, or `nullptr` if no object is owned.
```c++
template
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.*
### `class factory`
```c++
template >
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](https://github.com/andreasxp/prindex) library:
```c++
#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:
```c++
pl::factory f;
f.insert();
f.insert();
f.insert();

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
```
#### Member functions
```c++
template void insert();
```
Inserts the type `Derived` into the factory. This function must be called at least once before `poly` can be made with this instance of the factory.
```c++
std::vector list() const;
```
Returns a list of all types, registered in the factory, as a `std::vector` of `std::string`s that are used to `make` objects.
```c++
poly make(const std::string& name) const;
```
Makes a `poly`, 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.
### `class compound`
```c++
template
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
1. Provide either or both:
* a constructor from `const T*`, where `T` is the type on which the class operates;
* a default constructor.

If both constructors are provided, the class will be instantiated with `const T*`.
2. An `operator()`:
* For `Cloner`, `operator()` accepts `const T*` and returns a pointer to a copied object;
* For `Deleter`, `operator()` accepts `T*`, destroys the object, and returns nothing.

All pre-defined policies were made using `compound`.
### `unique`
```c++
template
using unique = compound::value,
std::default_delete,
pmr_delete>::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.
### `deep`
```c++
template
using deep = compound,
typename std::conditional::value,
std::default_delete,
pmr_delete>::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.
## License
This project is licenced under the MIT licence. It is free for personal and commercial use.