I'm trying to implement simplistic fast memory management for my delegate type, but encountered circular dependency which I can't solve myself.
// Size of single bucket of delegates
static constexpr size_t _alloc_buffer_bucket_size = 128;
template <typename TFunc, typename... Args>
struct bucket;
template <typename TFunc, typename... Args>
class Delegate final : public IDelegate
{
. . .
static void* operator new (size_t size)
{
auto bckt = _current_bucket; // gives compilation error here
}
private:
// Size of single bucket of delegates
static constexpr size_t _alloc_buffer_bucket_size = 128;
struct bucket
{
// stores currently occupied slots in bucket.
size_t allocated_slots = 0;
// when this bucket is filling with values, this
size_t current_slot = 0;
std::array<Delegate<TFunc, Args...>, _alloc_buffer_bucket_size> slots = {};
};
// Vector of buckets.
inline static std::vector<bucket*> _alloc_buffer = { new bucket(), };
// This is a pointer to currently filling bucket. New delegates are added here
// by delegate allocator.
// When this bucket reaches _alloc_buffer_bucket_size of items, this pointer is
// replaced with either newly allocated bucket or existing and empty bucket.
inline static bucket* _current_bucket = &(_alloc_buffer[0]);
};
This gives me "'Delegate<void (*)(void)>::bucket::slots' uses undefined class 'std::array<Delegate<void (*)(void)>,128>'
".
How can I break this circular dependency? I've tried using more pointers, moving bucket outside of Delegate scope, but this error remains.
CodePudding user response:
By moving the definition of the bucket
struct outside of the scope of Delegate
and assigning the _alloc_buffer
and _current_bucket
variables outside of the class scope, we can achieve compilation. I was able to get it compiling as such:
// Size of single bucket of delegates
static constexpr std::size_t _alloc_buffer_bucket_size = 128;
template <typename TFunc, typename... Args>
struct bucket;
template <typename TFunc, typename... Args>
class Delegate final : public IDelegate
{
public:
Delegate() = default;
static void* operator new (std::size_t size)
{
auto bckt = _current_bucket; // gives compilation error here
return nullptr; // added to get rid of the error
}
private:
// Vector of buckets.
static std::vector<bucket<TFunc, Args...>*> _alloc_buffer;
// This is a pointer to currently filling bucket. New delegates are added here
// by delegate allocator.
// When this bucket reaches _alloc_buffer_bucket_size of items, this pointer is
// replaced with either newly allocated bucket or existing and empty bucket.
static bucket<TFunc, Args...>* _current_bucket;
};
template <typename TFunc, typename... Args>
struct bucket
{
// stores currently occupied slots in bucket.
std::size_t allocated_slots = 0;
// when this bucket is filling with values, this
std::size_t current_slot = 0;
std::array<Delegate<TFunc, Args...>, _alloc_buffer_bucket_size> slots = {};
};
template <typename TFunc, typename... Args>
std::vector<bucket<TFunc, Args...>*> Delegate<TFunc, Args...>::_alloc_buffer = { new bucket<TFunc, Args...>(), };
template <typename TFunc, typename... Args>
bucket<TFunc, Args...>* Delegate<TFunc, Args...>::_current_bucket = &(_alloc_buffer[0]);
By splitting up the declaration and definition of the static variables and by splitting out the bucket type, the Delegate
class is defined by the time we go to actually use it. See it in action here: https://godbolt.org/z/bx7hh1av9
Edit: Tested this minimum sample on MSVC and Clang.