void* create() {
return new char[128];
}
void release(void* &p) {
delete[] p;
p = nullptr;
}
void use(void* p) {
cout << p << endl;
}
void* g_p = nullptr;
void useAndRemember(void* p) {
g_p = p;
cout << g_p << endl;
}
void useRemembered() {
cout << g_p << endl;
}
void* p = create();
use(p);
useAndRemember(p);
useRemembered();
release(p);
use(p);
useRemembered();
Output:
0000000000209B70
0000000000209B70
0000000000209B70
0000000000000000
0000000000209B70
p
?p
?template <class T>
class SmartPtr
{
public:
explicit SmartPtr(T* pointee) : m_pPointee(pointee) { }
~SmartPtr() { delete m_pPointee; }
T& operator* () const { return *m_pPointee; }
T* operator->() const { return m_pPointee; }
private:
T* m_pPointee;
};
{
SmartPtr<Enemy> spEnemy(new Enemy());
spEnemy->attack();
(*spEnemy).flee();
} // destructor is called -> enemy is deleted
=> Smart Pointers are most commonly used for ownership management and reference counting!
template <class T>
class SmarterPtr
{
public:
// constructors
SmarterPtr();
SmarterPtr(T* p);
SmarterPtr(const SmarterPtr& other);
SmarterPtr(SmarterPtr&& other);
// destructor
~SmarterPtr();
// assignment operators
SmarterPtr<T>& operator=(T* p);
SmarterPtr<T>& operator=(const SmarterPtr& rh);
SmarterPtr<T>& operator=(SmarterPtr&& rh);
// pointer semantics
T& operator* () const;
T* operator->() const;
};
=> A generic smart pointer should use intrusive reference counting where available and implement a non-intrusive reference counting scheme as an acceptable alternative.
struct Named
{
char m_pName[4];
Named(const char* name) { memcpy(m_pName, name, 3); m_pName[3]=0; }
~Named() { printf("deleting '%s'\n", m_pName); }
};
#include <memory>
#define BEGIN { printf("{\n");
#define END } printf("}\n");
weak_ptr <Named> wp1;
BEGIN
unique_ptr<Named> up1(new Named("uni"));
shared_ptr<Named> sp1(new Named("shr"));
BEGIN
unique_ptr<Named> up2 = std::move(up1);
END // object 'uni' is destroyed
wp1 = sp1; // assign weak pointer
BEGIN
shared_ptr<Named> sp2 = sp1;
END // reference count is decremnted
cout << wp1.lock() << endl;
END // object 'shr' is destroyed
cout << wp1.lock() << endl; // weak pointer is expired
Output:
(line breaks and line indents were modified for better readability)
{
{
deleting 'uni'
}
{
}
00000000003DFAF0
deleting 'shr'
}
0000000000000000
Object
RefCounted
Ref
RefCounted
WeakRef
RefCounted
without contributing to the reference counterImportant: Even a simple increment operation, e.g. i++
, is subject to data races! Internally, the CPU does three steps: load, increment, and store.
#include <atomic>
struct AtomicCounter
{
atomic_uint value;
void increment() { ++value; }
void decrement() { --value; }
unsigned int get() const { return value.load(); }
};
// thread safe
AtomicCounter ac = { 0 };
ac.increment(); PRINT(ac.get());
ac.decrement(); PRINT(ac.get());
SafeRefCount
// core/object/ref_counted.h
class SafeRefCount {
SafeNumeric<uint32_t> count;
// ...
}
template <class T>
class SafeNumeric {
std::atomic<T> value;
// ...
public:
// ...
T increment() {
return value.fetch_add(1, std::memory_order_acq_rel) + 1;
}
// Returns the original value instead of the new one
T postincrement() {
return value.fetch_add(1, std::memory_order_acq_rel);
}
T decrement() {
return value.fetch_sub(1, std::memory_order_acq_rel) - 1;
}
// ...
}
mutable
members: can be changed inside const
member functions// example taken from http://msdn.microsoft.com/en-us/library/4h2h0ktk.aspx
class X
{
public:
bool GetFlag() const
{
m_accessCount++;
return m_flag;
}
//private:
bool m_flag;
mutable int m_accessCount;
};
int _tmain(int argc, _TCHAR* argv[])
{
const X x;
x.GetFlag();
x.GetFlag();
x.GetFlag();
printf("%i\n", x.m_accessCount); // output: 3
return 0;
}
mutable
, for example, in its Variant containers Array
and Dictionary
in order to...const
member functions// core/variant/array.h
class Array {
mutable ArrayPrivate *_p;
void _unref() const;
public:
void _ref(const Array &p_from) const;
Variant &operator[](int p_idx);
const Variant &operator[](int p_idx) const;
// ...
void make_read_only();
bool is_read_only() const;
// ...
Array();
~Array();
};
// core/variant/array.cpp
class ArrayPrivate {
public:
SafeRefCount refcount;
Vector<Variant> array;
Variant *read_only = nullptr;
ContainerTypeValidate typed;
};
void Array::_ref(const Array &p_from) const {
ArrayPrivate *_fp = p_from._p;
// ...
bool success = _fp->refcount.ref(); // Radicke: ref() is not const
// ...
_unref();
_p = _fp;
}
void Array::_unref() const {
//...
if (_p->refcount.unref()) { // Radicke: unref() is not const
// ...
}
_p = nullptr;
}
// ...
Array::Array() {
_p = memnew(ArrayPrivate);
_p->refcount.init();
}
Array::~Array() {
_unref();
}
new
and delete
class A
{
public:
A() { printf("constructed\n"); }
~A() { printf("destructed\n"); }
void* operator new(size_t size) // regular new
{
printf("regular new\n");
return ::new char[sizeof(A)];
}
void* operator new(size_t size, void* pWhere) // placement new
{
printf("placement new\n");
return pWhere;
}
};
A* pA1 = new A();
delete pA1;
void* pBuffer = malloc(sizeof(A));
A* pA2 = new (pBuffer) A();
pA2->~A();
free(pBuffer);
Output:
regular new
constructed
destructed
placement new
constructed
destructed
memnew
is used to hook into its custom memory management system// core/object/ref_counted.h
template <class T>
class Ref {
// ...
void instantiate() {
ref(memnew(T));
}
// ...
};
Problem: Old invalid handles can index to newly occupied entries in the pointer table
Solution: Unique Ids for the handles (hash value or counter)
It can be beneficial, to combine the handle index and the hash into a single number using bit-fields.
union HandleIdx
{
struct
{
unsigned int index : 24; // bit field: 24 bits are used
unsigned int hash : 8; // bit field: 8 bits are used
};
unsigned int both; // 24 + 8 = 32 bits in total
};
// compile-time assertion checking
static_assert(sizeof(HandleIdx) == 4, "HandleIdx should be 4 bytes in size");
HandleIdx h1 = { 0, 128 };
HandleIdx h2 = { 1, 129 };
HandleIdx h3 = { 1, 130 };
cout << (h1.index == h2.index) << endl; // false
cout << (h1.both == h2.both ) << endl; // false
cout << (h1.index == h3.index) << endl; // false
cout << (h1.both == h3.both ) << endl; // false
cout << (h2.index == h3.index) << endl; // true
cout << (h2.both == h3.both ) << endl; // false
// core/object/ref_counted.h
class WeakRef : public RefCounted {
// ...
ObjectID ref;
// ...
public:
Variant get_ref() const;
void set_obj(Object *p_object);
void set_ref(const Ref<RefCounted> &p_ref);
WeakRef() {}
};
// core/object/ref_counted.cpp
Variant WeakRef::get_ref() const {
// ...
Object *obj = ObjectDB::get_instance(ref);
if (!obj) { return Variant(); }
// ...
return obj;
}
void WeakRef::set_obj(Object *p_object) {
ref = p_object ? p_object->get_instance_id() : ObjectID();
}
void WeakRef::set_ref(const Ref<RefCounted> &p_ref) {
ref = p_ref.is_valid() ? p_ref->get_instance_id() : ObjectID();
}
// core/object/object_id.h
class ObjectID {
uint64_t id = 0;
public:
bool is_ref_counted() const {
return (id & (uint64_t(1) << 63)) != 0;
}
bool is_valid() const { return id != 0; }
bool is_null() const { return id == 0; }
// ...
ObjectID() {}
explicit ObjectID(const uint64_t p_id) { id = p_id; }
explicit ObjectID(const int64_t p_id) { id = p_id; }
};
// core/object/object.h
class Object {
// ...
ObjectID _instance_id;
// ...
};
class ObjectDB {
// ...
struct ObjectSlot { // 128 bits per slot.
uint64_t validator : OBJECTDB_VALIDATOR_BITS; // Radicke: 39
uint64_t next_free : OBJECTDB_SLOT_MAX_COUNT_BITS; // Radicke: 24
uint64_t is_ref_counted : 1;
Object *object = nullptr;
};
// ...
static ObjectSlot *object_slots; // Radicke: handle index table
// ...
friend class Object;
// ...
// Radicke: finds a slot, makes an entry and creates an ObjectID
static ObjectID add_instance(Object *p_object);
// Radicke: removes the object from its slot
static void remove_instance(Object *p_object);
//...
}