티스토리 뷰
https://foonathan.net/2015/10/allocatorawarecontainer-propagation-pitfalls/
길고 이해하기 힘들다
filament에서 'propagate_on_container_move_assignment' 가 나와있어서 살펴봤는데,
move assign 시 allocator도 move assign 하면서 container의 자료의 소유권을
이전해라 라는 것 같은데...
https://stackoverflow.com/questions/27471053/example-usage-of-propagate-on-container-move-assignment
막상 filament의 구현은 allocator에서 사용하는 area memory 가
move/copy가 되지 않아 테스트 불가;
https://github.com/llvm-mirror/libcxx/blob/master/include/vector
template <class _Tp, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
vector<_Tp, _Allocator>&
vector<_Tp, _Allocator>::operator=(vector&& __x)
_NOEXCEPT_((__noexcept_move_assign_container<_Allocator, __alloc_traits>::value))
{
__move_assign(__x, integral_constant<bool,
__alloc_traits::propagate_on_container_move_assignment::value>());
return *this;
}
move assign이 2경우로 나뉘는데,
(propagate_on_container_move_assignment 에 따라)
하나는 false_type 이고 하나는 true_type 이다
alloc 을 추가로 비교했을 때 같지 않을 때 move_iterator를 이용한 assign을 탄다
template <class _Tp, class _Allocator>
void
vector<_Tp, _Allocator>::__move_assign(vector& __c, false_type)
_NOEXCEPT_(__alloc_traits::is_always_equal::value)
{
if (__base::__alloc() != __c.__alloc())
{
typedef move_iterator<iterator> _Ip;
assign(_Ip(__c.begin()), _Ip(__c.end()));
}
else
__move_assign(__c, true_type());
}
assign은 벡터에 새로운 내용을 집어 넣는다.벡터 객체에 이전에 있었던 원소들은 모두 삭제하고, 인자로 받은 새로운 내용을 집어 넣는다.
아래는 가장 best 케이스,
대입받는곳을 할당해제만하고
begin/end만 대입하고 끝
allocator를 move 해서
template <class _Tp, class _Allocator>
void
vector<_Tp, _Allocator>::__move_assign(vector& __c, true_type)
_NOEXCEPT_(is_nothrow_move_assignable<allocator_type>::value)
{
__vdeallocate();
__base::__move_assign_alloc(__c); // this can throw
this->__begin_ = __c.__begin_;
this->__end_ = __c.__end_;
this->__end_cap() = __c.__end_cap();
__c.__begin_ = __c.__end_ = __c.__end_cap() = nullptr;
#if _LIBCPP_DEBUG_LEVEL >= 2
__get_db()->swap(this, &__c);
#endif
}
__move_assign_alloc도 아래 조건에 따라 구분한다
__alloc_traits::propagate_on_container_move_assignment::value>());}
_LIBCPP_INLINE_VISIBILITY
void __move_assign_alloc(__vector_base& __c, true_type)
_NOEXCEPT_(is_nothrow_move_assignable<allocator_type>::value)
{
__alloc() = _VSTD::move(__c.__alloc());
}
_LIBCPP_INLINE_VISIBILITY
void __move_assign_alloc(__vector_base&, false_type)
_NOEXCEPT
{}
실제로 제공된 container에 대한 테스트
VS2019에서 동일한 allocator의 경우,
TEST(MemoryArena, allocator2)
{
MemoryArena arena;
AreaAllocator<int> alloc(&arena);
using vec = std::vector<int, AreaAllocator<int>>;
vec h0(10, 1, alloc);
vec h1(10, 2, alloc);
h1 = std::move(h0);
}
만약 move_assignment를 false로 해둔다면, no propagate를 타지만,
void _Move_assign(vector& _Right, _No_propagate_allocators) {
if (_Getal() == _Right._Getal()) {
_Move_assign(_Right, _Equal_allocators{});
} else {
결국 equal로 보낸다,
template <class _Alloc>
void _Pocma(_Alloc& _Left, _Alloc& _Right) noexcept { // (maybe) propagate on container move assignment
if constexpr (allocator_traits<_Alloc>::propagate_on_container_move_assignment::value) {
_Left = _STD move(_Right);
} else {
(void) _Left; // TRANSITION, VSO#486357
(void) _Right; // TRANSITION, VSO#486357
}
}
VS는 저기서 다시 check해서 allocator도 move 하지 않는다;
allocator 해제는, _Pocma 호출전 _Tidy 에서 수행한다
void _Tidy() noexcept { // free all storage
auto& _My_data = _Mypair._Myval2;
pointer& _Myfirst = _My_data._Myfirst;
pointer& _Mylast = _My_data._Mylast;
pointer& _Myend = _My_data._Myend;
_My_data._Orphan_all();
if (_Myfirst) { // destroy and deallocate old array
_Destroy(_Myfirst, _Mylast);
_Getal().deallocate(_Myfirst, static_cast<size_type>(_Myend - _Myfirst));
_Myfirst = pointer();
_Mylast = pointer();
_Myend = pointer();
}
}
만약, alloc을 다른걸 준다면?
TEST(MemoryArena, allocator2)
{
MemoryArena arena;
MemoryArena arena2;
AreaAllocator<int> alloc(&arena);
AreaAllocator<int> alloc2(&arena2);
using vec = std::vector<int, AreaAllocator<int>>;
vec h0(10, 1, alloc);
vec h1(10, 2, alloc2);
h1 = std::move(h0);
}
아예 다른 allocator 고 크기가 작을 경우,
기존 공간 삭제 후 크기를 늘려 할당한다
auto _Oldsize = static_cast<size_type>(_Mylast - _Myfirst);
if (_Newsize > _Oldsize) {
if (_Newsize > _Oldcapacity) { // reallocate
_Clear_and_reserve_geometric(_Newsize);
_Oldsize = 0;
}
const pointer _Mid = _First + _Oldsize;
_Move_unchecked(_First, _Mid, _Myfirst);
_Mylast = _Umove(_Mid, _Last, _Mylast);
} else {
const pointer _Newlast = _Myfirst + _Newsize;
_Move_unchecked(_First, _Last, _Myfirst);
_Destroy(_Newlast, _Mylast);
_Mylast = _Newlast;
}
}
아니면 그냥 copy로 확보된 dest의 메모리에 쓴다
아래는 2019의 패치내역, 여전히 바뀐다
struct _Equal_allocators {}; // usually allows contents to be stolen (e.g. with swap)
using _Propagate_allocators = true_type; // usually allows the allocator to be propagated, and then contents stolen
using _No_propagate_allocators = false_type; // usually turns moves into copies
컨테이너는 is_always_equal로 선언된 할당자에 대해서도 propagate_on_container_copy_assignment, propagate_on_container_move_assignment 및 propagate_on_container_swap에 따라 할당자를 항상 복사/이동/스왑하도록 수정되었습니다.
버전별로 먼가 바뀌는것 같은데. 실제로 쓰기전에 확인 필요
- Total
- Today
- Yesterday