티스토리 뷰

카테고리 없음

propagate_on_container_XXX

newpolaris 2020. 2. 11. 14:14

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
링크