util/IntrusiveList: add enum LinkMode

Compile-time code simplification.
This commit is contained in:
Max Kellermann 2022-11-12 08:52:58 +01:00
parent 3023816491
commit fb5d77158a
14 changed files with 132 additions and 81 deletions

View File

@ -46,7 +46,7 @@ class RemoteTagCache final {
struct Item final struct Item final
: public boost::intrusive::unordered_set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>, : public boost::intrusive::unordered_set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>,
public IntrusiveListHook, public IntrusiveListHook<>,
RemoteTagHandler RemoteTagHandler
{ {
RemoteTagCache &parent; RemoteTagCache &parent;

View File

@ -53,7 +53,7 @@ class Client final
friend struct ClientPerPartitionListHook; friend struct ClientPerPartitionListHook;
friend class ClientList; friend class ClientList;
IntrusiveListHook list_siblings, partition_siblings; IntrusiveListHook<> list_siblings, partition_siblings;
CoarseTimerEvent timeout_event; CoarseTimerEvent timeout_event;

View File

@ -51,7 +51,7 @@ static constexpr unsigned DEVICE_PLAYLIST = -3;
class SongFilter; class SongFilter;
struct Directory : IntrusiveListHook { struct Directory : IntrusiveListHook<> {
/* Note: the #IntrusiveListHook is protected with the global /* Note: the #IntrusiveListHook is protected with the global
#db_mutex. Read access in the update thread does not need #db_mutex. Read access in the update thread does not need
protection. */ protection. */

View File

@ -39,7 +39,7 @@ class ArchiveFile;
* A song file inside the configured music directory. Internal * A song file inside the configured music directory. Internal
* #SimpleDatabase class. * #SimpleDatabase class.
*/ */
struct Song : IntrusiveListHook { struct Song : IntrusiveListHook<> {
/* Note: the #IntrusiveListHook is protected with the global /* Note: the #IntrusiveListHook is protected with the global
#db_mutex. Read access in the update thread does not need #db_mutex. Read access in the update thread does not need
protection. */ protection. */

View File

@ -40,7 +40,9 @@ class EventLoop;
* thread that runs the #EventLoop, except where explicitly documented * thread that runs the #EventLoop, except where explicitly documented
* as thread-safe. * as thread-safe.
*/ */
class SocketEvent final : IntrusiveListHook, public EventPollBackendEvents class SocketEvent final
: IntrusiveListHook<IntrusiveHookMode::NORMAL>,
public EventPollBackendEvents
{ {
friend class EventLoop; friend class EventLoop;
friend struct IntrusiveListBaseHookTraits<SocketEvent>; friend struct IntrusiveListBaseHookTraits<SocketEvent>;

View File

@ -29,7 +29,7 @@
* A lease for an #InputCacheItem. * A lease for an #InputCacheItem.
*/ */
class InputCacheLease class InputCacheLease
: public IntrusiveListHook : public IntrusiveListHook<>
{ {
InputCacheItem *item = nullptr; InputCacheItem *item = nullptr;

View File

@ -40,7 +40,8 @@
namespace Uring { namespace Uring {
class CancellableOperation : public IntrusiveListHook class CancellableOperation
: public IntrusiveListHook<IntrusiveHookMode::NORMAL>
{ {
Operation *operation; Operation *operation;

View File

@ -27,7 +27,7 @@
template<typename T> template<typename T>
class CancellablePointer class CancellablePointer
: public IntrusiveListHook : public IntrusiveListHook<>
{ {
public: public:
typedef T *pointer; typedef T *pointer;

View File

@ -79,7 +79,7 @@ class UPnPDeviceDirectory final : UpnpCallback {
}; };
class Downloader final class Downloader final
: public IntrusiveListHook, CurlResponseHandler : public IntrusiveListHook<>, CurlResponseHandler
{ {
InjectEvent defer_start_event; InjectEvent defer_start_event;

View File

@ -34,7 +34,7 @@ class HttpdOutput;
class HttpdClient final class HttpdClient final
: BufferedSocket, : BufferedSocket,
public IntrusiveListHook public IntrusiveListHook<>
{ {
/** /**
* The httpd output object this client is connected to. * The httpd output object this client is connected to.

View File

@ -33,7 +33,7 @@ struct SnapcastTime;
class SnapcastOutput; class SnapcastOutput;
class UniqueSocketDescriptor; class UniqueSocketDescriptor;
class SnapcastClient final : BufferedSocket, public IntrusiveListHook class SnapcastClient final : BufferedSocket, public IntrusiveListHook<>
{ {
SnapcastOutput &output; SnapcastOutput &output;

View File

@ -0,0 +1,56 @@
/*
* Copyright 2022 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
/**
* Specifies the mode in which a hook for intrusive containers
* operates. This is meant to be used as a template argument to the
* hook class (e.g. #IntrusiveListHook).
*/
enum class IntrusiveHookMode {
/**
* No implicit initialization.
*/
NORMAL,
/**
* Keep track of whether the item is currently linked, allows
* using method is_linked(). This requires implicit
* initialization and requires iterating all items when
* deleting them which adds a considerable amount of overhead.
*/
TRACK,
/**
* Automatically unlinks the item in the destructor. This
* implies #TRACK and adds code to the destructor.
*/
AUTO_UNLINK,
};

View File

@ -30,6 +30,7 @@
#pragma once #pragma once
#include "Cast.hxx" #include "Cast.hxx"
#include "IntrusiveHookMode.hxx"
#include "MemberPointer.hxx" #include "MemberPointer.hxx"
#include "OptionalCounter.hxx" #include "OptionalCounter.hxx"
@ -47,6 +48,7 @@ struct IntrusiveListNode {
} }
}; };
template<IntrusiveHookMode _mode=IntrusiveHookMode::NORMAL>
class IntrusiveListHook { class IntrusiveListHook {
template<typename T> friend struct IntrusiveListBaseHookTraits; template<typename T> friend struct IntrusiveListBaseHookTraits;
template<auto member> friend struct IntrusiveListMemberHookTraits; template<auto member> friend struct IntrusiveListMemberHookTraits;
@ -56,13 +58,33 @@ protected:
IntrusiveListNode siblings; IntrusiveListNode siblings;
public: public:
IntrusiveListHook() noexcept = default; static constexpr IntrusiveHookMode mode = _mode;
IntrusiveListHook() noexcept {
if constexpr (mode >= IntrusiveHookMode::TRACK)
siblings.next = nullptr;
}
~IntrusiveListHook() noexcept {
if constexpr (mode >= IntrusiveHookMode::AUTO_UNLINK)
if (is_linked())
unlink();
}
IntrusiveListHook(const IntrusiveListHook &) = delete; IntrusiveListHook(const IntrusiveListHook &) = delete;
IntrusiveListHook &operator=(const IntrusiveListHook &) = delete; IntrusiveListHook &operator=(const IntrusiveListHook &) = delete;
void unlink() noexcept { void unlink() noexcept {
IntrusiveListNode::Connect(*siblings.prev, *siblings.next); IntrusiveListNode::Connect(*siblings.prev, *siblings.next);
if constexpr (mode >= IntrusiveHookMode::TRACK)
siblings.next = nullptr;
}
bool is_linked() const noexcept {
static_assert(mode >= IntrusiveHookMode::TRACK);
return siblings.next != nullptr;
} }
private: private:
@ -75,52 +97,27 @@ private:
} }
}; };
/** using SafeLinkIntrusiveListHook =
* A variant of #IntrusiveListHook which keeps track of whether it is IntrusiveListHook<IntrusiveHookMode::TRACK>;
* currently in a list. using AutoUnlinkIntrusiveListHook =
*/ IntrusiveListHook<IntrusiveHookMode::AUTO_UNLINK>;
class SafeLinkIntrusiveListHook : public IntrusiveListHook {
public:
SafeLinkIntrusiveListHook() noexcept {
siblings.next = nullptr;
}
void unlink() noexcept {
IntrusiveListHook::unlink();
siblings.next = nullptr;
}
bool is_linked() const noexcept {
return siblings.next != nullptr;
}
};
/** /**
* A variant of #IntrusiveListHook which auto-unlinks itself from the * Detect the hook type which is embedded in the given type as a base
* list upon destruction. As a side effect, it has an is_linked() * class. This is a template to postpone the type checks, to allow
* method.
*/
class AutoUnlinkIntrusiveListHook : public SafeLinkIntrusiveListHook {
public:
~AutoUnlinkIntrusiveListHook() noexcept {
if (is_linked())
unlink();
}
};
/**
* Detect the hook type; this is important because
* SafeLinkIntrusiveListHook::unlink() needs to clear the "next"
* pointer. This is a template to postpone the type checks, to allow
* forward-declared types. * forward-declared types.
*/ */
template<typename U> template<typename U>
struct IntrusiveListHookDetection { struct IntrusiveListHookDetection {
static_assert(std::is_base_of_v<IntrusiveListHook, U>); /* TODO can this be simplified somehow, without checking for
all possible enum values? */
using type = std::conditional_t<std::is_base_of_v<SafeLinkIntrusiveListHook, U>, using type = std::conditional_t<std::is_base_of_v<IntrusiveListHook<IntrusiveHookMode::NORMAL>, U>,
SafeLinkIntrusiveListHook, IntrusiveListHook<IntrusiveHookMode::NORMAL>,
IntrusiveListHook>; std::conditional_t<std::is_base_of_v<IntrusiveListHook<IntrusiveHookMode::TRACK>, U>,
IntrusiveListHook<IntrusiveHookMode::TRACK>,
std::conditional_t<std::is_base_of_v<IntrusiveListHook<IntrusiveHookMode::AUTO_UNLINK>, U>,
IntrusiveListHook<IntrusiveHookMode::AUTO_UNLINK>,
void>>>;
}; };
/** /**
@ -131,10 +128,6 @@ struct IntrusiveListBaseHookTraits {
template<typename U> template<typename U>
using Hook = typename IntrusiveListHookDetection<U>::type; using Hook = typename IntrusiveListHookDetection<U>::type;
static constexpr bool IsAutoUnlink() noexcept {
return std::is_base_of_v<AutoUnlinkIntrusiveListHook, T>;
}
static constexpr T *Cast(IntrusiveListNode *node) noexcept { static constexpr T *Cast(IntrusiveListNode *node) noexcept {
auto *hook = &Hook<T>::Cast(*node); auto *hook = &Hook<T>::Cast(*node);
return static_cast<T *>(hook); return static_cast<T *>(hook);
@ -152,14 +145,12 @@ template<auto member>
struct IntrusiveListMemberHookTraits { struct IntrusiveListMemberHookTraits {
using T = MemberPointerContainerType<decltype(member)>; using T = MemberPointerContainerType<decltype(member)>;
using _Hook = MemberPointerType<decltype(member)>; using _Hook = MemberPointerType<decltype(member)>;
using Hook = typename IntrusiveListHookDetection<_Hook>::type;
static constexpr bool IsAutoUnlink() noexcept { template<typename Dummy>
return std::is_base_of_v<AutoUnlinkIntrusiveListHook, _Hook>; using Hook = _Hook;
}
static constexpr T *Cast(IntrusiveListNode *node) noexcept { static constexpr T *Cast(IntrusiveListNode *node) noexcept {
auto &hook = Hook::Cast(*node); auto &hook = Hook<T>::Cast(*node);
return &ContainerCast(hook, member); return &ContainerCast(hook, member);
} }
@ -176,14 +167,15 @@ template<typename T,
typename HookTraits=IntrusiveListBaseHookTraits<T>, typename HookTraits=IntrusiveListBaseHookTraits<T>,
bool constant_time_size=false> bool constant_time_size=false>
class IntrusiveList { class IntrusiveList {
template<typename U>
using Hook = typename IntrusiveListHookDetection<U>::type;
IntrusiveListNode head{&head, &head}; IntrusiveListNode head{&head, &head};
[[no_unique_address]] [[no_unique_address]]
OptionalCounter<constant_time_size> counter; OptionalCounter<constant_time_size> counter;
static constexpr auto GetHookMode() noexcept {
return HookTraits::template Hook<T>::mode;
}
static constexpr T *Cast(IntrusiveListNode *node) noexcept { static constexpr T *Cast(IntrusiveListNode *node) noexcept {
return HookTraits::Cast(node); return HookTraits::Cast(node);
} }
@ -234,7 +226,7 @@ public:
} }
~IntrusiveList() noexcept { ~IntrusiveList() noexcept {
if constexpr (std::is_base_of_v<SafeLinkIntrusiveListHook, T>) if constexpr (GetHookMode() >= IntrusiveHookMode::TRACK)
clear(); clear();
} }
@ -283,7 +275,7 @@ public:
} }
void clear() noexcept { void clear() noexcept {
if constexpr (std::is_base_of_v<SafeLinkIntrusiveListHook, T>) { if constexpr (GetHookMode() >= IntrusiveHookMode::TRACK) {
/* for SafeLinkIntrusiveListHook, we need to /* for SafeLinkIntrusiveListHook, we need to
remove each item manually, or else its remove each item manually, or else its
is_linked() method will not work */ is_linked() method will not work */
@ -504,7 +496,7 @@ public:
void insert(iterator p, reference t) noexcept { void insert(iterator p, reference t) noexcept {
static_assert(!constant_time_size || static_assert(!constant_time_size ||
!HookTraits::IsAutoUnlink(), GetHookMode() < IntrusiveHookMode::AUTO_UNLINK,
"Can't use auto-unlink hooks with constant_time_size"); "Can't use auto-unlink hooks with constant_time_size");
auto &existing_node = ToNode(*p); auto &existing_node = ToNode(*p);

View File

@ -36,17 +36,17 @@
namespace { namespace {
template<typename Hook> template<IntrusiveHookMode mode>
struct CharItem final : Hook { struct CharItem final : IntrusiveListHook<mode> {
char ch; char ch;
constexpr CharItem(char _ch) noexcept:ch(_ch) {} constexpr CharItem(char _ch) noexcept:ch(_ch) {}
}; };
template<typename Hook> template<IntrusiveHookMode mode>
static std::string static std::string
ToString(const IntrusiveList<CharItem<Hook>> &list, ToString(const IntrusiveList<CharItem<mode>> &list,
typename IntrusiveList<CharItem<Hook>>::const_iterator it, typename IntrusiveList<CharItem<mode>>::const_iterator it,
std::size_t n) noexcept std::size_t n) noexcept
{ {
std::string result; std::string result;
@ -55,10 +55,10 @@ ToString(const IntrusiveList<CharItem<Hook>> &list,
return result; return result;
} }
template<typename Hook> template<IntrusiveHookMode mode>
static std::string static std::string
ToStringReverse(const IntrusiveList<CharItem<Hook>> &list, ToStringReverse(const IntrusiveList<CharItem<mode>> &list,
typename IntrusiveList<CharItem<Hook>>::const_iterator it, typename IntrusiveList<CharItem<mode>>::const_iterator it,
std::size_t n) noexcept std::size_t n) noexcept
{ {
std::string result; std::string result;
@ -71,7 +71,7 @@ ToStringReverse(const IntrusiveList<CharItem<Hook>> &list,
TEST(IntrusiveList, Basic) TEST(IntrusiveList, Basic)
{ {
using Item = CharItem<IntrusiveListHook>; using Item = CharItem<IntrusiveHookMode::NORMAL>;
Item items[]{'a', 'b', 'c'}; Item items[]{'a', 'b', 'c'};
@ -103,9 +103,9 @@ TEST(IntrusiveList, Basic)
ASSERT_EQ(ToStringReverse(list, list.begin(), 6), "a_cfea"); ASSERT_EQ(ToStringReverse(list, list.begin(), 6), "a_cfea");
} }
TEST(IntrusiveList, SafeLink) TEST(IntrusiveList, Track)
{ {
using Item = CharItem<SafeLinkIntrusiveListHook>; using Item = CharItem<IntrusiveHookMode::TRACK>;
Item items[]{'a', 'b', 'c'}; Item items[]{'a', 'b', 'c'};
@ -162,7 +162,7 @@ TEST(IntrusiveList, SafeLink)
TEST(IntrusiveList, AutoUnlink) TEST(IntrusiveList, AutoUnlink)
{ {
using Item = CharItem<AutoUnlinkIntrusiveListHook>; using Item = CharItem<IntrusiveHookMode::AUTO_UNLINK>;
Item a{'a'}; Item a{'a'};
ASSERT_FALSE(a.is_linked()); ASSERT_FALSE(a.is_linked());
@ -194,7 +194,7 @@ TEST(IntrusiveList, AutoUnlink)
TEST(IntrusiveList, Merge) TEST(IntrusiveList, Merge)
{ {
using Item = CharItem<IntrusiveListHook>; using Item = CharItem<IntrusiveHookMode::NORMAL>;
const auto predicate = [](const Item &a, const Item &b){ const auto predicate = [](const Item &a, const Item &b){
return a.ch < b.ch; return a.ch < b.ch;
@ -229,7 +229,7 @@ TEST(IntrusiveList, Merge)
TEST(IntrusiveList, Sort) TEST(IntrusiveList, Sort)
{ {
using Item = CharItem<IntrusiveListHook>; using Item = CharItem<IntrusiveHookMode::NORMAL>;
const auto predicate = [](const Item &a, const Item &b){ const auto predicate = [](const Item &a, const Item &b){
return a.ch < b.ch; return a.ch < b.ch;