include: add C++ wrapper for C shared resources
authorRomain Vimont <rom1v@videolabs.io>
Tue, 11 Sep 2018 10:48:29 +0000 (12:48 +0200)
committerThomas Guillem <thomas@gllm.fr>
Thu, 15 Nov 2018 13:34:04 +0000 (14:34 +0100)
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 */

include/vlc_cxx_helpers.hpp
src/Makefile.am
src/test/shared_data_ptr.cpp [new file with mode: 0644]

index 832a964..ed80797 100644 (file)
@@ -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
index 4c7f7a6..42a60be 100644 (file)
@@ -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 \
diff --git a/src/test/shared_data_ptr.cpp b/src/test/shared_data_ptr.cpp
new file mode 100644 (file)
index 0000000..d261fff
--- /dev/null
@@ -0,0 +1,149 @@
+#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;
+}