Commit 40c6d71f authored by Romain Vimont's avatar Romain Vimont Committed by Thomas Guillem
Browse files

include: add C++ wrapper for C shared resources

Add a helper to create RAII wrappers for C shared resources, which
automatically call the Hold() and Release() functions associated to the
raw pointer.

Just declare a new shared resource wrapper type:

    using InputItemPtr = vlc_shared_data_ptr_type(input_item_t,
                                                  input_item_Hold,
                                                  input_item_Release);

Then use this new type directly:

    input_item_t *ptr = /* ... */;
    InputItemPtr item(ptr);
    QString name = item->psz_name;
    InputItemPtr other = item; /* hold automatically */
parent 4c2779a4
......@@ -122,6 +122,143 @@ inline std::unique_ptr<T[], void (*)(void*)> wrap_carray( T* ptr ) noexcept
} // anonymous namespace
///
/// Wraps a C shared resource having associated Hold() and Release() functions
//
/// This is a RAII wrapper for C shared resources (which are manually managed by
/// calling explicitly their Hold() and Release() functions).
///
/// The Hold() and Release() functions must accept exactly one parameter having
/// type T* (the raw pointer type). Their return type is irrelevant.
///
/// To create a new shared resource wrapper type for my_type_t, simply declare:
///
/// using MyTypePtr =
/// vlc_shared_data_ptr_type(my_type_t, my_type_Hold, my_type_Release);
///
/// Then use it to wrap a raw C pointer:
///
/// my_type_t *raw_ptr = /* ... */;
/// MyTypePtr ptr(raw_ptr);
// In C++17, the template declaration could be replaced by:
// template<typename T, auto HOLD, auto RELEASE>
template <typename T, typename H, typename R, H HOLD, R RELEASE>
class vlc_shared_data_ptr {
T *ptr = nullptr;
public:
/* default implicit constructor */
vlc_shared_data_ptr() = default;
/**
* Wrap a shared resource.
*
* If the pointer is not nullptr, and hold is true, then the resource is
* hold (the caller shared ownership is preserved).
* If hold is false, then the caller transfers the ownership to this
* wrapper.
*
* \param ptr the raw pointer (can be nullptr)
* \param hold whether the resource must be hold
*/
explicit vlc_shared_data_ptr(T *ptr, bool hold = true)
: ptr(ptr)
{
if (ptr && hold)
HOLD(ptr);
}
vlc_shared_data_ptr(const vlc_shared_data_ptr &other)
: vlc_shared_data_ptr(other.ptr) {}
vlc_shared_data_ptr(vlc_shared_data_ptr &&other) noexcept
: ptr(other.ptr)
{
other.ptr = nullptr;
}
~vlc_shared_data_ptr()
{
if (ptr)
RELEASE(ptr);
}
vlc_shared_data_ptr &operator=(const vlc_shared_data_ptr &other)
{
reset(other.ptr, true);
return *this;
}
vlc_shared_data_ptr &operator=(vlc_shared_data_ptr &&other) noexcept
{
reset(other.ptr, false);
other.ptr = nullptr;
return *this;
}
bool operator==(const vlc_shared_data_ptr &other) const
{
return ptr == other.ptr;
}
bool operator!=(const vlc_shared_data_ptr &other) const
{
return !(*this == other);
}
operator bool() const
{
return ptr;
}
T &operator*() const
{
return *ptr;
}
T *operator->() const
{
return ptr;
}
T *get() const
{
return ptr;
}
/**
* Reset the shared resource.
*
* ptr.reset(rawptr, hold);
*
* is semantically equivalent to:
*
* ptr = vlc_shared_data_ptr<...>(rawptr, hold);
*
* If the pointer is not nullptr, and hold is true, then the resource is
* hold (the caller shared ownership is preserved).
* If hold is false, then the caller transfers the ownership to this
* wrapper.
*
* \param ptr the raw pointer (can be nullptr)
* \param hold whether the resource must be hold
*/
void reset(T *newptr = nullptr, bool hold = true)
{
if (newptr && hold)
HOLD(newptr);
if (ptr)
RELEASE(ptr);
ptr = newptr;
}
};
// useful due to the unnecessarily complex template declaration before C++17
#define vlc_shared_data_ptr_type(type, hold, release) \
::vlc::vlc_shared_data_ptr<type, decltype(&hold), decltype(&release), \
&hold, &release>
#ifdef VLC_THREADS_H_
namespace threads
......
......@@ -535,7 +535,8 @@ check_PROGRAMS = \
test_headers \
test_mrl_helpers \
test_arrays \
test_vector
test_vector \
test_shared_data_ptr
TESTS = $(check_PROGRAMS) check_symbols
......@@ -559,6 +560,7 @@ test_headers_SOURCES = test/headers.c
test_mrl_helpers_SOURCES = test/mrl_helpers.c
test_arrays_SOURCES = test/arrays.c
test_vector_SOURCES = test/vector.c
test_shared_data_ptr_SOURCES = test/shared_data_ptr.cpp
AM_LDFLAGS = -no-install
LDADD = libvlccore.la \
......
#include <assert.h>
#include <algorithm>
#include <vector>
#include <vlc_cxx_helpers.hpp>
struct mockrc
{
int count = 1;
};
static void mockrc_Hold(mockrc *p)
{
assert(p->count > 0);
p->count++;
}
static void mockrc_Release(mockrc *p)
{
assert(p->count > 0);
p->count--;
}
using MockRcPtr = vlc_shared_data_ptr_type(mockrc, mockrc_Hold, mockrc_Release);
static void test_raii()
{
mockrc mock;
{
MockRcPtr ptr(&mock, true);
assert(ptr);
assert(mock.count == 2);
assert(ptr->count == 2);
{
mockrc_Hold(&mock);
/* transfer the ownership */
MockRcPtr ptr2(&mock, false);
assert(ptr->count == 3);
}
assert(ptr->count == 2);
}
assert(mock.count == 1);
}
static void test_assignment()
{
mockrc mock;
MockRcPtr ptr(&mock, false);
assert(mock.count == 1);
{
MockRcPtr ptr2 = ptr;
assert(mock.count == 2);
{
MockRcPtr ptr3 = std::move(ptr2);
assert(mock.count == 2);
/* assign a nullptr, this releases the previous one */
ptr3 = {};
assert(mock.count == 1);
ptr3 = MockRcPtr(&mock);
assert(mock.count == 2);
MockRcPtr ptr4(std::move(ptr3));
assert(mock.count == 2);
}
/* ptr4 is the only wrapper alive (ptr3 had been moved), count is
* decremented by 1 */
assert(mock.count == 1);
}
/* ptr2 had been moved, no decrement */
assert(mock.count == 1);
ptr = ptr; /* self-assignement should have no effect */
assert(mock.count == 1);
}
static void test_reset()
{
mockrc mock;
{
MockRcPtr ptr(&mock, false);
assert(mock.count == 1);
/* hold once, release once, count is not changed */
ptr.reset(&mock, true);
assert(mock.count == 1);
mockrc_Hold(&mock);
assert(mock.count == 2);
ptr.reset();
assert(mock.count == 1);
ptr.reset(&mock, false);
assert(mock.count == 1);
}
/* ownership had transferred to ptr, which has been destroyed */
assert(mock.count == 0);
}
static void test_vector()
{
mockrc mock;
{
std::vector<MockRcPtr> vec(10, MockRcPtr(&mock, false));
assert(mock.count == 10);
std::random_shuffle(vec.begin(), vec.end());
assert(mock.count == 10);
{
auto vec2 = vec;
assert(mock.count == 20);
vec2.push_back(MockRcPtr(&mock, true));
assert(mock.count == 21);
}
assert(mock.count == 10);
vec.erase(vec.begin() + 2, vec.begin() + 6);
assert(mock.count == 6);
vec.emplace_back(&mock, true);
assert(mock.count == 7);
}
/* ownership was transferred to vec constructor */
assert(mock.count == 0);
}
int main() {
test_raii();
test_assignment();
test_reset();
test_vector();
return 0;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment