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
: public boost::intrusive::unordered_set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>,
public IntrusiveListHook,
public IntrusiveListHook<>,
RemoteTagHandler
{
RemoteTagCache &parent;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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