From: Casey Bodley Date: Fri, 3 Apr 2020 14:41:10 +0000 (-0400) Subject: common: add ceph::allocate_unique() X-Git-Tag: v17.0.0~1186^2~17 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=3e5ecffceb2d0614ded5ad3d329bb14c33b853b3;p=ceph.git common: add ceph::allocate_unique() adds an allocator-aware version of std::make_unique(). this is similar to std::allocate_shared(), though it's slightly less convenient because, unlike std::shared_ptr, the Deleter has to be specified as a template parameter in std::unique_ptr Signed-off-by: Casey Bodley --- diff --git a/src/common/allocate_unique.h b/src/common/allocate_unique.h new file mode 100644 index 0000000000000..4c5cc1c6ebbcf --- /dev/null +++ b/src/common/allocate_unique.h @@ -0,0 +1,69 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#pragma once + +#include + +namespace ceph { + +/// An allocator-aware 'Deleter' for std::unique_ptr. The +/// allocator's traits must have a value_type of T. +template +class deallocator { + using allocator_type = Alloc; + using allocator_traits = std::allocator_traits; + using pointer = typename allocator_traits::pointer; + allocator_type alloc; + public: + explicit deallocator(const allocator_type& alloc) noexcept : alloc(alloc) {} + void operator()(pointer p) { + allocator_traits::destroy(alloc, p); + allocator_traits::deallocate(alloc, p, 1); + } +}; + +/// deallocator alias that rebinds Alloc's value_type to T +template +using deallocator_t = deallocator + ::template rebind_alloc>; + +/// std::unique_ptr alias that rebinds Alloc if necessary, and avoids repetition +/// of the template parameter T. +template +using allocated_unique_ptr = std::unique_ptr>; + + +/// Returns a std::unique_ptr whose memory is managed by the given allocator. +template +static auto allocate_unique(Alloc& alloc, Args&&... args) + -> allocated_unique_ptr +{ + static_assert(!std::is_array_v, "allocate_unique() does not support T[]"); + + using allocator_type = typename std::allocator_traits + ::template rebind_alloc; + using allocator_traits = std::allocator_traits; + auto a = allocator_type{alloc}; + auto p = allocator_traits::allocate(a, 1); + try { + allocator_traits::construct(a, p, std::forward(args)...); + return {p, deallocator{a}}; + } catch (...) { + allocator_traits::deallocate(a, p, 1); + throw; + } +} + +} // namespace ceph diff --git a/src/test/common/CMakeLists.txt b/src/test/common/CMakeLists.txt index 3bb6d3f8644a5..4bf46a00873d0 100644 --- a/src/test/common/CMakeLists.txt +++ b/src/test/common/CMakeLists.txt @@ -337,6 +337,10 @@ add_ceph_unittest(unittest_cdc) add_executable(unittest_ceph_timer test_ceph_timer.cc) add_ceph_unittest(unittest_ceph_timer) + add_executable(unittest_blocked_completion test_blocked_completion.cc) add_ceph_unittest(unittest_blocked_completion) target_link_libraries(unittest_blocked_completion Boost::system GTest::GTest) + +add_executable(unittest_allocate_unique test_allocate_unique.cc) +add_ceph_unittest(unittest_allocate_unique) diff --git a/src/test/common/test_allocate_unique.cc b/src/test/common/test_allocate_unique.cc new file mode 100644 index 0000000000000..09944951baa3e --- /dev/null +++ b/src/test/common/test_allocate_unique.cc @@ -0,0 +1,97 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "common/allocate_unique.h" +#include +#include +#include + +namespace { + +// allocation events recorded by logging_allocator +struct event { + size_t size; + bool allocated; // true for allocate(), false for deallocate() +}; +using event_log = std::vector; + +template +struct logging_allocator { + event_log *const log; + + using value_type = T; + + explicit logging_allocator(event_log *log) : log(log) {} + logging_allocator(const logging_allocator& other) : log(other.log) {} + + template + logging_allocator(const logging_allocator& other) : log(other.log) {} + + T* allocate(size_t n, const void* hint=0) + { + auto p = std::allocator{}.allocate(n, hint); + log->emplace_back(event{n * sizeof(T), true}); + return p; + } + void deallocate(T* p, size_t n) + { + std::allocator{}.deallocate(p, n); + if (p) { + log->emplace_back(event{n * sizeof(T), false}); + } + } +}; + +} // anonymous namespace + +namespace ceph { + +TEST(AllocateUnique, Allocate) +{ + event_log log; + auto alloc = logging_allocator{&log}; + { + auto p = allocate_unique(alloc, 'a'); + static_assert(std::is_same_v>>>); + ASSERT_TRUE(p); + ASSERT_EQ(1, log.size()); + EXPECT_EQ(1, log.front().size); + EXPECT_EQ(true, log.front().allocated); + } + ASSERT_EQ(2, log.size()); + EXPECT_EQ(1, log.back().size); + EXPECT_EQ(false, log.back().allocated); +} + +TEST(AllocateUnique, RebindAllocate) +{ + event_log log; + auto alloc = logging_allocator{&log}; + { + auto p = allocate_unique(alloc, "a"); + static_assert(std::is_same_v>>>); + ASSERT_TRUE(p); + ASSERT_EQ(1, log.size()); + EXPECT_EQ(sizeof(std::string), log.front().size); + EXPECT_EQ(true, log.front().allocated); + } + ASSERT_EQ(2, log.size()); + EXPECT_EQ(sizeof(std::string), log.back().size); + EXPECT_EQ(false, log.back().allocated); +} + +} // namespace ceph