Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use counted pointer to hold values #326

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
12 changes: 6 additions & 6 deletions Kernel/Abstractions/blackbox.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
#ifndef BLACKBOX_H
#define BLACKBOX_H
#include "basic.hpp"
#include "sharedptr.hpp"

/**
* @brief A template class representing an opaque pointer.
*/
class blackbox_rep : public abstract_struct {
class blackbox_rep {
public:
inline blackbox_rep () {}
inline virtual ~blackbox_rep () {}
Expand All @@ -22,11 +23,10 @@ class blackbox_rep : public abstract_struct {
virtual tm_ostream& display (tm_ostream& out)= 0;
};

class blackbox {
public:
ABSTRACT_NULL (blackbox);
class blackbox : public counted_ptr<blackbox_rep, true> {
using base::counted_ptr;
template <typename T> friend blackbox close_box (const T&);
};
ABSTRACT_NULL_CODE (blackbox);

template <class T> class whitebox_rep : public blackbox_rep {
public:
Expand Down Expand Up @@ -121,7 +121,7 @@ type_box (blackbox bb) {
template <class T>
blackbox
close_box (const T& data) {
return tm_new<whitebox_rep<T>> (data);
return blackbox (blackbox::make<whitebox_rep<T>> (data));
}

/**
Expand Down
198 changes: 198 additions & 0 deletions Kernel/Abstractions/sharedptr.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/** \file sharedptr.hpp
* \copyright GPLv3
* \details Defines smart pointers with reference counting. No spin lock is
* applied.
* \author jingkaimori
* \date 2024
*/

#pragma once

#include "fast_alloc.hpp"
#include <utility>

struct ref_counter_base {
virtual void inc_count ()= 0;
virtual void dec_count ()= 0;
virtual void* get () = 0;
};

/**
* @brief Structure representing an object with a reference count.
* @tparam T Actual type of inner object, must be a complete type
*/
template <typename T> struct ref_counter : ref_counter_base {
/// @brief the reference count of the object
int ref_count;
T content;

/**
* @brief in-place construct an object, and set its reference count to 1.
* @tparam Params types of parameters required by constructor of object
*/
template <typename... Params>
explicit ref_counter (Params&&... p)
: ref_count (1), content (std::forward<Params> (p)...){};

void inc_count () { ref_count++; }
/**
* @brief decrement the reference count of the object and delete it if the
* count reaches 0.
*/
void dec_count () {
ref_count--;
if (ref_count == 0) {
tm_delete (this);
}
}
void* get () { return &content; }
};

/**
* @brief Smart pointer with reference counting.
* @details
* To use smart pointer, counted_ptr class should be inherited publicly, and the
* type exploited to user of the smart pointer should be provided. If null
* pointer may be held inside the smart pointer, parameter nullable should be
* set to true explictly.
*
* The class derived from pointer should provide proper constructor so that user
* can instantiate pointer from arguments of other type. Operator should be
* overloaded at the derived class, rather than the type of underlying object.
* @tparam T The type which user of pointer get from indirection of the smart
* pointer.
*/
template <typename T, bool nullable= false> class counted_ptr {

protected:
using counter_t= ref_counter_base;
/**
* @brief short-hand name of the class, avoiding duplicated type name of
* underlying object inside template. If the derived class is also template
* class, this declaration should be imported explictly with `using` clause.
*/
using base= counted_ptr<T, nullable>;
explicit counted_ptr (counter_t* c)
: counter (c), rep (static_cast<T*> (c->get ())) {}
/**
* @tparam Stored the real type of underlying object, may differ from types
* exploits to user. This type must be complete type.
*/
template <typename Stored= T, typename... Params>
static counter_t* make (Params&&... p) {
return tm_new<ref_counter<Stored>> (std::forward<Params> (p)...);
}

private:
/**
* @brief Opaque pointer to the counter that holds the instance of the
* underlying object
*/
counter_t* counter;

public:
/**
* cached pointer to the underlying object
*/
T* rep;

counted_ptr () : counter (nullptr), rep (nullptr) {
static_assert (nullable,
"null pointer is not allowed in non-null smart pointer.");
}
/**
* copy constructor of the smart pointer
*/
counted_ptr (const counted_ptr<T, nullable>& x)
: counter (x.counter), rep (x.rep) {
if constexpr (nullable) {
if (counter != nullptr) {
counter->inc_count ();
}
}
else {
counter->inc_count ();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this statement in two branchs looks like same?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only difference is null checking

}
}
~counted_ptr () {
if constexpr (nullable) {
if (counter != nullptr) {
counter->dec_count ();
}
}
else {
counter->dec_count ();
}
}
/**
* @brief Decrement the reference count for the old object (`*this`) and
* increments the reference count for the new object.
* @param x the new object
*/
counted_ptr<T, nullable>& operator= (counted_ptr<T, nullable>& x) {
if constexpr (nullable) {
if (x.counter != nullptr) {
x.counter->inc_count ();
}
if (this->counter != nullptr) {
this->counter->dec_count ();
}
}
else {
x.counter->inc_count ();
this->counter->dec_count ();
}
this->counter= x.counter;
this->rep = x.rep;
return *this;
}
/**
* @brief Steal underlying reference counter from rvalue temprary object, then
* leave the old counter (`this->counter`) in the temprary object. Finally the
* temprary object will be destroyed along with old object held by `*this`
* @param x the new object
*/
counted_ptr<T, nullable>& operator= (counted_ptr<T, nullable>&& x) {
std::swap (this->counter, x.counter);
std::swap (this->rep, x.rep);
return *this;
}
/**
* @brief pointer dereference operator
*/
T* operator->() {
if constexpr (nullable) {
if (counter != nullptr) {
return rep;
}
else {
return nullptr;
}
}
else {
return rep;
}
}
/**
* @brief pointer dereference operator, call from const pointer to get const
* object
*/
const T* operator->() const { return operator->(); }
bool is_nil () const {
if constexpr (nullable) {
return counter == nullptr;
}
else {
return false;
}
}
};

/**
* @brief adapter of legacy is_nil function.
*/
template <typename T>
inline bool
is_nil (const counted_ptr<T, true> x) {
return x.is_nil ();
}
31 changes: 10 additions & 21 deletions Kernel/Containers/hashtree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
#define HASHTREE_H

#include "hashmap.hpp"
#include "sharedptr.hpp"

template <class K, class V> class hashtree;
template <class K, class V> int N (hashtree<K, V> tree);
template <class K, class V> bool is_nil (hashtree<K, V> tree);

template <class K, class V> class hashtree_rep : concrete_struct {
template <class K, class V> class hashtree_rep {
hashmap<K, hashtree<K, V>> children;

public:
Expand Down Expand Up @@ -75,38 +76,27 @@ template <class K, class V> class hashtree_rep : concrete_struct {
* of NULL pointers so to speak). These NULL nodes are created by passing
* a boolean value to the hashtree constructor. One cannot accidentally
* obtain a NULL element by e.g. accessing a child (see below).
*
* In general, I tried to imitate the TeXmacs-way of memory management
* as closely as possibly, however the workaround is not that pretty.
* As more elegant way might be to modify the hashmap class so that
* a hashmap contains only a pointer to a function that returns
* a default value instead of a instance of a value-element.
* But I didn't want to modify core TeXmacs code.
******************************************************************************/

template <class K, class V> class hashtree {
// CONCRETE_TEMPLATE_2(hashtree,K,V);
hashtree_rep<K, V>* rep;
template <class K, class V>
class hashtree : public counted_ptr<hashtree_rep<K, V>, true> {
using base= typename counted_ptr<hashtree_rep<K, V>, true>::base;

// this constructor always returns a NULL element
inline hashtree (bool) : rep (NULL) {}
inline hashtree (bool) : base () {}

// ensures that this hashtree has a rep
void realize ();

public:
inline hashtree (const hashtree<K, V>&);
inline ~hashtree ();
inline hashtree<K, V>& operator= (hashtree<K, V> x);

// default constructor returns a non-NULL node, which does not have a value
inline hashtree () : rep (tm_new<hashtree_rep<K, V>> ()) {}
inline hashtree () : base (base::make ()) {}

// returns a non-NULL node, that has value
inline hashtree (V val) : rep (tm_new<hashtree_rep<K, V>> (val)) {}
inline hashtree (V val) : base (base::make (val)) {}

// returns this node's value
inline hashtree_rep<K, V>* operator->(void);
inline hashtree_rep<K, V>* operator->();

// returns this node's child with the label "key". If the node doesn't
// have such a child, an error is raised.
Expand All @@ -120,8 +110,7 @@ template <class K, class V> class hashtree {
inline hashtree<K, V> operator[] (K key); // rw access

friend class hashtree_rep<K, V>;
friend bool is_nil<K, V> (hashtree<K, V> ht);
friend int N<K, V> (hashtree<K, V> ht);
friend int N<K, V> (hashtree<K, V> ht);
};

#include "hashtree.ipp"
Expand Down
36 changes: 4 additions & 32 deletions Kernel/Containers/hashtree.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,6 @@
#define HASHTREE_C
#include "hashtree.hpp"

/******************************************************************************
* Methods normally provided by
* CONCRETE_TEMPLATE_2_CODE(hashtree,class,K,class,V);
******************************************************************************/

template <class K, class V>
inline hashtree<K, V>::hashtree (const hashtree<K, V>& x) : rep (x.rep) {
if (this->rep != NULL) INC_COUNT (this->rep);
}

template <class K, class V> inline hashtree<K, V>::~hashtree () {
if (this->rep != NULL) DEC_COUNT (this->rep);
}

template <class K, class V>
inline hashtree<K, V>&
hashtree<K, V>::operator= (hashtree<K, V> x) {
if (this->rep != NULL) DEC_COUNT (this->rep);
this->rep= x.rep;
if (x.rep != NULL) INC_COUNT (x.rep);
return *this;
}

/******************************************************************************
* Methods of hashtree_rep<K,V>
******************************************************************************/

template <class K, class V>
inline bool
hashtree_rep<K, V>::contains (K key) {
Expand Down Expand Up @@ -81,9 +54,8 @@ hashtree_rep<K, V>::get_label () {
template <class K, class V>
inline void
hashtree<K, V>::realize () {
if (rep == NULL) {
rep= tm_new<hashtree_rep<K, V>> ();
INC_COUNT (rep);
if (this->is_nil ()) {
*this= hashtree ();
}
}

Expand All @@ -96,7 +68,7 @@ inline hashtree_rep<K, V>*
hashtree<K, V>::operator->(void) {
// always make sure there is a rep!
realize ();
return rep;
return this->rep;
}

template <class K, class V>
Expand All @@ -121,7 +93,7 @@ hashtree<K, V>::operator() (K key) {
template <class K, class V>
inline bool
is_nil (hashtree<K, V> ht) {
return ht.rep == NULL;
return ht.is_nil ();
}

template <class K, class V>
Expand Down
Loading
Loading