diff --git a/libcxx/include/memory b/libcxx/include/memory --- a/libcxx/include/memory +++ b/libcxx/include/memory @@ -3342,7 +3342,13 @@ void __shared_ptr_emplace<_Tp, _Alloc>::__on_zero_shared() _NOEXCEPT { +#if _LIBCPP_STD_VER > 17 + typedef typename __allocator_traits_rebind<_Alloc, _Tp>::type _TpAllocator; + _TpAllocator __alloc(__get_alloc()); + allocator_traits<_TpAllocator>::destroy(__alloc, __get_elem()); +#else __get_elem()->~_Tp(); +#endif } template @@ -4068,28 +4074,25 @@ return shared_ptr<_Tp>::__create_with_control_block(__ptr, __hold2.release()); } -template -inline _LIBCPP_INLINE_VISIBILITY -typename enable_if -< - !is_array<_Tp>::value, - shared_ptr<_Tp> ->::type -allocate_shared(const _Alloc& __a, _Args&& ...__args) +template::value> > +_LIBCPP_INLINE_VISIBILITY +shared_ptr<_Tp> allocate_shared(const _Alloc& __a, _Args&& ...__args) { - static_assert( is_constructible<_Tp, _Args...>::value, "Can't construct object in allocate_shared"); + typedef __shared_ptr_emplace<_Tp, _Alloc> _ControlBlock; + typedef typename __allocator_traits_rebind<_Alloc, _ControlBlock>::type _ControlBlockAllocator; + typedef __allocator_destructor<_ControlBlockAllocator> _GuardDeleter; - typedef __shared_ptr_emplace<_Tp, _Alloc> _CntrlBlk; - typedef typename __allocator_traits_rebind<_Alloc, _CntrlBlk>::type _A2; - typedef __allocator_destructor<_A2> _D2; + _ControlBlockAllocator __cb_alloc(__a); + unique_ptr<_ControlBlock, _GuardDeleter> __guard(__cb_alloc.allocate(1), _GuardDeleter(__cb_alloc, 1)); - _A2 __a2(__a); - unique_ptr<_CntrlBlk, _D2> __hold2(__a2.allocate(1), _D2(__a2, 1)); - ::new(static_cast(_VSTD::addressof(*__hold2.get()))) - _CntrlBlk(__a, _VSTD::forward<_Args>(__args)...); +#if _LIBCPP_STD_VER > 17 + allocator_traits<_ControlBlockAllocator>::construct(__cb_alloc, _VSTD::addressof(*__guard.get()), __a, _VSTD::forward<_Args>(__args)...); +#else + ::new(static_cast(_VSTD::addressof(*__guard.get()))) _ControlBlock(__a, _VSTD::forward<_Args>(__args)...); +#endif - typename shared_ptr<_Tp>::element_type *__p = __hold2->__get_elem(); - return shared_ptr<_Tp>::__create_with_control_block(__p, _VSTD::addressof(*__hold2.release())); + typename shared_ptr<_Tp>::element_type *__p = __guard->__get_elem(); + return shared_ptr<_Tp>::__create_with_control_block(__p, _VSTD::addressof(*__guard.release())); } template diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp --- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp @@ -94,6 +94,24 @@ int Three::count = 0; +#if TEST_STD_VER < 20 +template +struct AllocNoConstruct : std::allocator +{ + AllocNoConstruct() = default; + + template + AllocNoConstruct(AllocNoConstruct) {} + + template + struct rebind { + typedef AllocNoConstruct other; + }; + + void construct(void*) { assert(false); } +}; +#endif // TEST_STD_VER < 20 + template void test() { @@ -161,5 +179,12 @@ } assert(A::count == 0); + // Test that we don't call construct before C++20. +#if TEST_STD_VER < 20 + { + (void)std::allocate_shared(AllocNoConstruct()); + } +#endif // TEST_STD_VER < 20 + return 0; } diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp @@ -0,0 +1,183 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// + +// shared_ptr + +// template +// shared_ptr allocate_shared(const A& a, Args&&... args); + +// This patch tests that allocator_traits::construct is used in allocate_shared +// as requested in C++20. + +#include "test_macros.h" + +#include +#include + +static bool construct_called = false; +static bool destroy_called = false; +static unsigned allocator_id = 0; + +template +struct MyAllocator { +public: + typedef T value_type; + typedef T* pointer; + + unsigned id = 0; + + MyAllocator() = default; + MyAllocator(int id) : id(id) {} + + template + MyAllocator(MyAllocator const& other) : id(other.id){}; + + pointer allocate(std::ptrdiff_t n) { + return pointer(static_cast(::operator new(n * sizeof(T)))); + } + + void deallocate(pointer p, std::ptrdiff_t) { return ::operator delete(p); } + + template + void construct(T* p, Args&& ...args) { + construct_called = true; + destroy_called = false; + allocator_id = id; + ::new (p) T(std::forward(args)...); + } + + void destroy(T* p) { + construct_called = false; + destroy_called = true; + allocator_id = id; + p->~T(); + } +}; + +#if !defined(_LIBCPP_VERSION) +struct Private; + +class Factory { +public: + static std::shared_ptr allocate(); +}; + +template +struct FactoryAllocator; + +struct Private { + int id; + +private: + friend FactoryAllocator; + Private(int id) : id(id) {} + ~Private() {} +}; + +template +struct FactoryAllocator : std::allocator { + FactoryAllocator() = default; + + template + FactoryAllocator(FactoryAllocator) {} + + template + struct rebind { + typedef FactoryAllocator other; + }; + + void construct(void* p, int id) { ::new (p) Private(id); } + void destroy(Private* p) { p->~Private(); } +}; + +std::shared_ptr Factory::allocate() { + FactoryAllocator factory_alloc; + return std::allocate_shared(factory_alloc, 42); +} +#endif // !libc++ + +struct mchar { + char c; +}; + +struct Foo { + int val; + + Foo(int val) : val(val) {} + + Foo(Foo a, Foo b) : val(a.val + b.val) {} +}; + +struct Bar { + std::max_align_t y; +}; + +void test_aligned(void* p, size_t align) { + assert(reinterpret_cast(p) % align == 0); +} + +int main(int, char**) { + { + std::shared_ptr p = std::allocate_shared(MyAllocator()); + assert(construct_called); + } + assert(destroy_called); + { + std::shared_ptr p = + std::allocate_shared(MyAllocator(), Foo(42), Foo(100)); + assert(construct_called); + assert(p->val == 142); + } + assert(destroy_called); + { // Make sure allocator is copied. + std::shared_ptr p = std::allocate_shared(MyAllocator(3)); + assert(allocator_id == 3); + + allocator_id = 0; + } + assert(allocator_id == 3); + + { + std::shared_ptr p = std::allocate_shared(MyAllocator(), 42); + assert(construct_called); + assert(*p == 42); + } + assert(destroy_called); + + { // Make sure allocator is properly re-bound. + std::shared_ptr p = + std::allocate_shared(MyAllocator(), 42); + assert(construct_called); + assert(*p == 42); + } + assert(destroy_called); + + // TODO: + // Libc++ doesn't support this yet because we inherit from Private transitively + // when trying to do EBO. Test this once we move to using [[no_unique_address]]. +#if !defined(_LIBCPP_VERSION) + { + // Make sure that we call the correct allocator::construct. Private has a private constructor + // so the construct method must be called on its friend Factory's allocator + // (Factory::Allocator). + std::shared_ptr p = Factory().allocate(); + assert(p->id == 42); + } +#endif + + { + std::shared_ptr p; + test_aligned(p.get(), alignof(Bar)); + } + + return 0; +}