diff --git a/libcxx/include/memory b/libcxx/include/memory --- a/libcxx/include/memory +++ b/libcxx/include/memory @@ -3191,6 +3191,22 @@ {__alloc_traits::deallocate(__alloc_, __p, __s_);} }; +template +class __allocator_destroyer +{ + typedef allocator_traits<_Alloc> __alloc_traits; + _Alloc __alloc_; +public: + _LIBCPP_INLINE_VISIBILITY __allocator_destroyer(_Alloc const& __a) + _NOEXCEPT + : __alloc_(__a) {} + + template + _LIBCPP_INLINE_VISIBILITY + void operator()(_Tp* __p) _NOEXCEPT + {__alloc_traits::destroy(__alloc_, __p);} +}; + template _ForwardIterator uninitialized_copy(_InputIterator __f, _InputIterator __l, _ForwardIterator __r) @@ -3481,6 +3497,20 @@ #endif } +template +struct __shared_ptr_aligned_block +{ + typename _VSTD::aligned_storage< + sizeof(_CntrlBlk), + _VSTD::alignment_of<_CntrlBlk>::value + >::type __cntrl_buff; + + typename _VSTD::aligned_storage< + sizeof(_Tp), + _VSTD::alignment_of<_Tp>::value + >::type __value_buff; +}; + template class _LIBCPP_TEMPLATE_VIS weak_ptr; class _LIBCPP_TYPE_VIS __shared_count @@ -3578,10 +3608,14 @@ : public __shared_weak_count { __compressed_pair<__compressed_pair<_Tp, _Dp>, _Alloc> __data_; + size_t __size; + public: _LIBCPP_INLINE_VISIBILITY - __shared_ptr_pointer(_Tp __p, _Dp __d, _Alloc __a) - : __data_(__compressed_pair<_Tp, _Dp>(__p, _VSTD::move(__d)), _VSTD::move(__a)) {} + __shared_ptr_pointer(_Tp __p, _Dp __d, _Alloc __a, + size_t __size = sizeof(__shared_ptr_pointer)) + : __data_(__compressed_pair<_Tp, _Dp>(__p, _VSTD::move(__d)), _VSTD::move(__a)), + __size(__size) {} #ifndef _LIBCPP_NO_RTTI virtual const void* __get_deleter(const type_info&) const _NOEXCEPT; @@ -3615,13 +3649,13 @@ void __shared_ptr_pointer<_Tp, _Dp, _Alloc>::__on_zero_shared_weak() _NOEXCEPT { - typedef typename __allocator_traits_rebind<_Alloc, __shared_ptr_pointer>::type _Al; + typedef typename __allocator_traits_rebind<_Alloc, char>::type _Al; typedef allocator_traits<_Al> _ATraits; typedef pointer_traits _PTraits; _Al __a(__data_.second()); __data_.second().~_Alloc(); - __a.deallocate(_PTraits::pointer_to(*this), 1); + __a.deallocate(_PTraits::pointer_to(*reinterpret_cast(this)), __size); } template @@ -4503,21 +4537,37 @@ !is_array<_Tp>::value, shared_ptr<_Tp> >::type -allocate_shared(const _Alloc& __a, _Args&& ...__args) +allocate_shared(const _Alloc& __alloc, _Args&& ...__args) { - static_assert( is_constructible<_Tp, _Args...>::value, "Can't construct object in allocate_shared"); + typedef typename __allocator_traits_rebind<_Alloc, _Tp>::type _AllocForTp; + typedef allocator_traits<_AllocForTp> _AllocTraitsForTp; + typedef __allocator_destroyer<_AllocForTp> _DestroyerForTp; - typedef __shared_ptr_emplace<_Tp, _Alloc> _CntrlBlk; - typedef typename __allocator_traits_rebind<_Alloc, _CntrlBlk>::type _A2; - typedef __allocator_destructor<_A2> _D2; + // The allocator here doesn't matter because it's going to be rebound before it's used. + typedef __shared_ptr_pointer<_Tp*, _DestroyerForTp, _Alloc> _CntrlBlk; + typedef __shared_ptr_aligned_block<_CntrlBlk, _Tp> _AlignedBlk; + typedef typename __allocator_traits_rebind<_Alloc, _AlignedBlk>::type _AllocForCntrlBlk; + typedef __allocator_destructor<_AllocForCntrlBlk> _DestroyerForCntrlBlk; - _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 + _AllocForTp __alloc_for_tp(__alloc); +#endif // _LIBCPP_STD_VER > 17 + _AllocForCntrlBlk __alloc_for_cntrl(__alloc); + unique_ptr<_AlignedBlk, _DestroyerForCntrlBlk> __holder(__alloc_for_cntrl.allocate(1), + _DestroyerForCntrlBlk(__alloc_for_cntrl, 1)); + _CntrlBlk* __cntrl_buff = reinterpret_cast<_CntrlBlk*>(&__holder.get()->__cntrl_buff); + _Tp* __value_buff = reinterpret_cast<_Tp*>(&__holder.get()->__value_buff); + + ::new(static_cast(__cntrl_buff)) + _CntrlBlk(__value_buff, _DestroyerForTp(__alloc), __alloc_for_cntrl, sizeof(_AlignedBlk)); - typename shared_ptr<_Tp>::element_type *__p = __hold2.get()->get(); - return shared_ptr<_Tp>::__create_with_control_block(__p, _VSTD::addressof(*__hold2.release())); +#if _LIBCPP_STD_VER > 17 + _AllocTraitsForTp::construct(__alloc_for_tp, __value_buff, _VSTD::forward<_Args>(__args)...); +#else + ::new(__value_buff) _Tp(_VSTD::forward<_Args>(__args)...); +#endif // _LIBCPP_STD_VER > 17 + __holder.release(); + return shared_ptr<_Tp>::__create_with_control_block(__value_buff, __cntrl_buff); } template diff --git a/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/function_type_default_deleter.fail.cpp b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/function_type_default_deleter.fail.cpp --- a/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/function_type_default_deleter.fail.cpp +++ b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/function_type_default_deleter.fail.cpp @@ -38,6 +38,8 @@ } // expected-error-re@memory:* 2 {{static_assert failed{{.*}} "default_delete cannot be instantiated for function types"}} { + // expected-error@memory:* {{no member named 'value' in '{{.*}}is_empty<{{.*}}__compressed_pair), {{.*}}default_delete)> > >'}} + // expected-error@memory:* {{cannot initialize return object of type '{{.*}}__shared_weak_count *' with an rvalue of type '{{.*}}__shared_ptr_pointer), {{.*}}default_delete)>, {{.*}}allocator<{{.*}}__shared_ptr_dummy_rebind_allocator_type> > *'}} SPtr<4> s4(getFn<4>()); // expected-note {{requested here}} SPtr<5> s5(getFn<5>(), std::default_delete>{}); // expected-note {{requested here}} } 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,189 @@ +//===----------------------------------------------------------------------===// +// +// 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); } + + void construct(T* p) { + construct_called = true; + destroy_called = false; + allocator_id = id; + ::new (p) T; + } + + void construct(T* p, T x) { + construct_called = true; + destroy_called = false; + allocator_id = id; + ::new (p) T(x); + } + + void construct(T* p, T x, T y) { + construct_called = true; + destroy_called = false; + allocator_id = id; + ::new (p) T(x, y); + } + + void destroy(T* p) { + destroy_called = true; + construct_called = false; + allocator_id = id; + p->~T(); + } +}; + +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); +} + +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); + + { + // 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); + } + + { + std::shared_ptr p; + test_aligned(p.get(), alignof(Bar)); + } + + return 0; +}