smart pointer for managing objects that belong to inheritance hierarchies
Whenever you wish to utilize object oriented programming paradigm of C++ you are sometimes forced to use pointers:
class BaseClass {
public:
int a;
virtual void foo() {
printf("base\n");
}
virtual ~BaseClass() = default;
};
class ChildClass : public BaseClass {
public:
int b;
void foo() override {
printf("child\n");
}
}
int main(void) {
ChildClass c;
BaseClass b = c; // c object will get trimmed to b object
b.foo(); // "base" will be printed
ChildClass* cptr = new ChildClass();
BaseClass* bptr = cptr;
bptr->foo(); // "child" will be printed
return 0;
}Problem arises when you want to store bunch of objects that inherit from some base class in a containter like std::vector:
std::vector<BaseClass*> bcv;
bcv.push_back(new ChildClass());
bcv2 = bcv; // now both bcv[0] and bcv2[0] point to the same object
// potential double free and other abnormalities can happen if you are not carefullNow try putting a vector like that as a member in a class that you wish to be Copy Assignable or Copy Constructable. You will not only need to define a destructuor (to free dynamically allocated data), but also Copy Assignment constructor and operator.
Here comes oop_ptr.
oop_ptr makes all objects easily constuctable, copiable and destructable.
Only caveat is, that you will need to define a special function in every non-abstract object in your inheritance hierarchy. Luckily oop_ptr.hpp contains macros to speed up the process.
#include <oop_ptr.hpp>
#include <cassert>
class BaseClass {
public:
int a;
virtual void foo() {
printf("base\n");
}
virtual ~BaseClass() = default;
oop_ptr_base_declare(BaseClass);
};
class ChildClass : public BaseClass {
public:
int b;
void foo() override {
printf("child\n");
}
oop_ptr_child_declare(BaseClass);
}
oop_ptr_define(BaseClass, BaseClass)
oop_ptr_define(BaseClass, ChildClass)
int main(void) {
oop_ptr<BaseClass> boop = oop_ptr<BaseClass>(new ChildClass()); // boop will take ownership over newly allocated ChildClass instance
boop->foo(); // "child" will be printed
boop2 = boop; // boop2 will allocate it's own instance of ChildClass using ChildClass default copy constructor
boop2->foo(); // "child" will be printed
poob->b = 1;
poob2->b = 2;
assert(poob->b != boop2->b); // boop and boop2 are independent - point to different objects
// no need to delete as both boop and boop2 will automatically dealocate managed resources when exiting scope
return 0;
}oop_ptr is a header only, single include utility. Just copy this file to your project's include directory.
with methods like release() and get() and overloaded -> and * operators
with #defines you can include / exclude methods, operators and constructors from oop_ptr definition
template<
typename T
> class oop_ptr;oop_ptr()- initializes managed object pointer tonullptroop_ptr(T* ptr)- takes ownership over object pointed byptroop_ptr(const T* ptr)- copies object pointed byptrinto newly allocated memory, disabled by default, defineOOP_PTR_PTR_COPY_CONSTRUCTORto enableoop_ptr(const T& obj)- copiesobjinto newly allocated memoryoop_ptr(const oop_ptr<T>& other)- copy constructoroop_ptr(oop_ptr<T>&& other)- move constructor
operator= (const oop_ptr<T>& other)- copy assignment operatoroperator= (oop_ptr<T>&& other)- move assignment operatoroperator bool- checks if there is an associated managed objectoperator*,operator->- dereferences pointer to the managed objectoperator=(const T& obj)- destroyes currently managed object (if any) and creates a copy of supplied object, disabled by default, to enable defineOOP_PTR_OBJECT_COPY_OPERATORoperator=(const T& obj)- destroyes currently managed object (if any) and creates a copy of object pointed byptr, disabled by default, to enable defineOOP_PTR_PTR_COPY_OPERATORoperator=(T* ptr)- destroyes currently managed object (if any) and takes ownership over object pointed byptr, disabled by default, to enable defineOOP_PTR_PTR_MOVE_OPERATOR
release()- returns a pointer to the managed object and releases the ownershipget()- returns a pointer to the managed object without releasing the ownershipgettable<U>()- checks if managed object is an instance ofU(withdynamic_cast)release<U>()- returns a pointer to the managed object, dynamic_casted toUand releases the ownershipget<U>()- returns a pointer to the managed object, dynamic_casted toU, without releasing the ownership
In following definitions replace b with name of the base class and c with name of the child class.
oop_ptr_base_declare(b);- meant to be put in base class declaration (for pure virtual method type:oop_ptr_base_declare(b) = 0;)oop_ptr_child_declare(b);- meant to be put in child class declarationoop_ptr_define(b, c)- creates method definition (for definition of base class useoop_ptr_define(b, b))oop_ptr_template_base_define(b)- meant to be put in base class declaration if the base class is a templateoop_ptr_template_child_define(b, c)- meant to be put in child class declaration if the base class is a template
| define name | created method | defined by default |
|---|---|---|
OOP_PTR_PTR_MOVE_CONSTRUCTOR |
oop_ptr(T* ptr) |
yes |
OOP_PTR_OBJECT_COPY_CONSTRUCTOR |
oop_ptr(const T& obj) |
yes |
OOP_PTR_PTR_COPY_CONSTRUCTOR |
oop_ptr(const T* ptr) |
no |
OOP_PTR_OBJECT_COPY_OPERATOR |
operator=(const T& obj) |
no |
OOP_PTR_PTR_COPY_OPERATOR |
operator=(const T* ptr) |
no |
OOP_PTR_PTR_MOVE_OPERATOR |
operator=(T* ptr) |
no |
If you wish to customize oop_ptr it is encouraged that you put your #defines or remove existing ones directly in oop_ptr.hpp file.
Warning
Do not define both: OOP_PTR_PTR_COPY_OPERATOR and OOP_PTR_PTR_MOVE_OPERATOR at once.
Do not define both: OOP_PTR_PTR_COPY_CONSTRUCTOR and OOP_PTR_PTR_MOVE_CONSTRUCTOR at once.
It is also possible to change name of the method generated by macros with oop_ptr_get_copy #define.
#define oop_ptr_get_copy awesomelyNamedMethodBy default (if oop_ptr_get_copy is not defined) macros will generate method named: get_copy_ptr
When in doubt look at the source code (it's not complicated).
Although oop_ptr was created to enabe storing objects that inherit from some base class in a C++ containers or as class members, this is not a particularly cache friendly approach, since oop_ptr still stores a pointer, that can point anywhere, potentially creating a cache miss. Because of this issue i plan to create a seperate utility that would store object directly (without any extra indirection), as a separete project. I will provide a link when it's ready.
With new reflection system for C++ 26 there should be a way to do all this without the need to define special functions in class definitions. I have plans for reimplementing oop_ptr using new reflection system as a separate smart pointer (oop_ptr26). Existing implementation will remain to be used in projects that don't want to switch to version 26 of C++.
Look into issues to see all planned features.
Here is a list of projects that utilize oop_ptr: