오른쪽 값 시리즈 의 5:이상 안전 한 이동

원문:http://cpp-next.com/archive/2009/10/exceptionally-moving/
C++의 효율 적 인 값 유형 에 관 한 시리즈 의 다섯 번 째 편 에 오신 것 을 환영 합 니 다.지난 편 에서 우 리 는 이전 할당 의 최 적 화 를 위 한 끊 임 없 는 검색 에 머 물 렀 다.오늘 우 리 는 이'도시 이전(Move City)'을 가로 지 르 는 길 을 찾 을 것 이다.여기 서 가장 일반적인 유형 은 놀 라 운 충돌 이 있 을 수 있다.
앞의 글 에서 우 리 는'이동 할 수 있 는 권한'을 제공 함으로써 같은 코드 를 이동 할 수 있 는 유형 과 이동 할 수 없 는 유형 에 동시에 사용 하고 가능 할 때 이동 최 적 화 를 이용 할 수 있 음 을 보 았 다.이러한'가능 할 때 이동 하고 필요 할 때 복사'방법 은 코드 최적화 에 매우 유용 하 며 구조 함 수 를 옮 기지 않 은 낡은 유형 에 도 호 환 된다.그러나 강 한 이상 보증 을 제공 하 는 조작 에 있어 서 는 새로운 부담 이 증가 했다.
강 이상 보증,강 이상 요구
강 한 이상 보증 을 실현 하려 면 특정한 작업 의 모든 절 차 를 두 가지 로 나 누 어야 한다.
  • 이상 을 던 질 수 있 으 나 역 변 할 수 없 는 조작 은 포함 되 지 않 습 니 다
  • 되 돌 릴 수 없 는 변 화 를 포함 할 수 있 지만 이상 한 조작 을 하지 않 습 니 다
  • 강 한 이상 보증 은 각 단계 의 조작 에 대한 분류 에 의존한다.
    만약 우리 가 모든 동작 을 이 두 가지 로 나 누고 그 어떠한 첫 번 째 동작 도 두 번 째 동작 전에 발생 하도록 보장 한다 면 우 리 는 아무 일 도 없 을 것 이다.C++03 에서 하나의 전형 적 인 예 가 있 습 니 다.vector:reserve()가 새로운 요소 에 메모 리 를 할당 해 야 할 때:
    void reserve(size_type n)
    {
        if (n > this->capacity())
        {
            pointer new_begin = this->allocate( n );
            size_type s = this->size(), i = 0;
            try
            {
                // copy to new storage: can throw; doesn't modify *this
                for (;i < s; ++i)
                     new ((void*)(new_begin + i)) value_type( (*this)[i] );
            }
            catch(...)
            {
                while (i > 0)                 // clean up new elements
                   (new_begin + --i)->~value_type();
     
                this->deallocate( new_begin );    // release storage
                throw;
            }
            // -------- irreversible mutation starts here -----------
            this->deallocate( this->begin_ );
            this->begin_ = new_begin;
            this->end_ = new_begin + s;
            this->cap_ = new_begin + n;
        }
    }

    이전 작업 을 지원 하 는 실현 이 라면 try 블록 에 std:move 의 명시 적 호출 을 추가 하고 순환 을 다음 으로 바 꿔 야 합 니 다.
    for (;i < s; ++i)
         new ((void*)(new_begin + i)) value_type( std::move( (*this)[i] ) );

    이 변화 에서 재 미 있 는 것 은 valuetype 은 이전 을 지원 합 니 다.그러면 순환 에서*this(왼쪽 값 에서 명시 적 이전 요청 을 하 는 것 은 논리 적 으로 바 꾸 는 작업 입 니 다)를 바 꿉 니 다.
    현재 이전 작업 이 이상 을 던 지면 이 순환 은 되 돌 릴 수 없 는 변화 가 생 길 것 이다.한 부분 을 되 돌려 야 하 는 순환 은 더 많은 이전 작업 이 필요 하기 때문이다.그래서 valuetype 은 이전 을 지원 하 는 상황 에서 강 한 이상 보증 을 유지 합 니 다.이전 구조 함 수 는 반드시 던 지지 않 아야 합 니 다.
      던 지 는 전이 작업 이 있 을 수 있 습 니 다.잠재 적 으로 다시 던 지 는 스크롤 백 을 할 수 없습니다.
    결실
    C++0x 표준 초안 에 서 는 기본적으로 던 질 수 있 는 전이 구조 함 수 를 반대 합 니 다.우 리 는 이 규칙 을 준수 하 는 것 을 권장 합 니 다.그러나 구조 함 수 를 옮 기 려 면 이 규칙 을 던 지지 않 아야 한다.항상 쉽게 지 키 는 것 은 아니다.std::pair<:string>을 예 로 들 면 User Type 은 복제 구조 함 수 를 던 질 수 있 는 형식 입 니 다.C++03 에서 이 유형 은 문제 가 없습니다.std::vector 에 사용 할 수 있 습 니 다.그러나 C++0x 에서 std:string 은 전이 구조 함 수 를 가지 고 있 습 니 다.std:pair 역시 있 습 니 다.
    template 
    pair::pair(pair&& x)
      : first(std::move(x.first))
      , second(std::move(x.second))
    {}

    여기 가 문제 야.second 의 유형 은 User Type 입 니 다.구조 함 수 를 옮 기지 않 았 습 니 다.이것 은 second 의 구 조 는 이전 구조 가 아니 라 복사 구조 임 을 의미 합 니 다.그래서 pair<:string usertype=">는 던 질 수 있 는 전이 구조 함 수 를 보 여 줍 니 다.std::vector 에서 강 한 이상 보증 을 파괴 하지 않 고 사용 할 수 없습니다.
    오늘 은 pair 를 사용 할 수 있 도록 다음 코드 와 유사 한 것 이 필요 하 다 는 것 을 의미 합 니 다.
    template 
    pair(pair&& rhs
      , typename enable_if<                 // Undocumented optional
            mpl::and_<                      // argument, not part of the
                boost::has_nothrow_move // public interface of pair.
              , boost::has_nothrow_move
            >
         >::type* = 0
    )
      : first(std::move(rhs.first)),
        second(std::move(rhs.second))
    {};

    enable 사용 하기if,이 구조 함 수 를"사라 지게"할 수 있 습 니 다.has 를 제외 하고.nothrow_move 는 T1 과 T2 모두 true 입 니 다.
    우 리 는 전이 구조 함수 가 존재 하 는 지 확인 할 방법 이 없다 는 것 을 알 고 있 습 니 다.던 지지 않 았 는 지 는 말 할 것 도 없습니다.따라서 우리 가 새로운 언어 특성 을 얻 기 전에 boost::hasnothrow_move 는 사용자 정의 형식 을 false 로 되 돌려 주 는 방법 중 하나 입 니 다.따라서 이전 구조 함 수 를 만 들 때 이 trait 를 특 화 해 야 합 니 다.예 를 들 어 만약 에 우리 가 std::vector 와 std::pair 에 전이 구조 함 수 를 추가 했다 면 우 리 는 다음 과 같이 추가 해 야 합 니 다.
    namespace boost
    {
        // All vectors have a (nothrow) move constructor
        template 
        struct has_nothrow_move<:vector> > : true_type {};
     
        // A pair has a (nothrow) move constructor iff both its
        // members do as well.
        template 
        struct has_nothrow_move<:pair> >
          : mpl::and_<
               boost::has_nothrow_move
             , boost::has_nothrow_move
            > {};
    }

    우 리 는 이것 이 매우 보기 싫다 는 것 을 인정한다.C++위원 회 는 아직도 이 문 제 를 어떻게 해결 할 것 인 가 를 토론 하고 있 지만 다음 과 같은 일 들 은 모두 일반적인 동 의 를 얻 었 다.
    4.567917.우 리 는 강 한 이상 보증 을 조용히 포기 했다 고 해서 기 존의 코드 를 파괴 해 서 는 안 된다
    4.567917.적당 한 시기 에 부족 한 전이 구조 함 수 를 생 성 할 수 있 습 니 다.Bjarne Stroustrup 이 N2904 에서 제안 한 것 처럼 이 문 제 를 줄 일 수 있 습 니 다.이것 은 pair 와 모든 유사 한 유형의 문 제 를 복구 할 수 있 고 생 성 된 이동 최 적 화 를 증가 함으로써 일부 코드 의 속 도 를 무료 로 향상 시 킬 수 있다
    4.567917.아니면 우리 가'수공'으로 처리 해 야 하 는 유형 이 있 습 니 다
    "문제 있 는 타 입".
    문제 가 있 는 유형 으로 돌아 가면 우리 가 옮 기 고 싶 은 하위 대상 을 가지 고 있 습 니 다.안전 한 실현 을 제 공 했 습 니 다.그리고 우리 가 필요 로 하 는 다른 하위 대상 도 있 습 니 다.std::vector 는 하나의 예 입 니 다.분배 기 를 가지 고 있 습 니 다.복사 구조 함수 가 이상 을 던 질 수 있 습 니 다.
    vector(vector&& rhs)
      : _alloc( std::move(rhs._alloc) )
      , _begin( rhs._begin )
      , _end( rhs._end )
      , _cap( rhs._cap )
    {
        // "something else"
        rhs._begin = rhs._end = rhs._cap = 0;
    }

    N2904 에서 말 한 결 성 생 성 된 것 과 같은 간단 한 구성원 식 이전 은 여기 서 정확 한 의 미 를 가지 지 못 할 것 이다.특히 rhs 의begin, _end 및cap 제로.하지만 만약alloc 는 던 지지 않 은 전이 구조 함수 가 없 으 면 두 번 째 줄 에서 만 복사 할 수 있 습 니 다.이 복사 가 이상 을 던 질 수 있다 면,vector 는 던 질 수 있 는 전이 구조 함 수 를 제공 합 니 다.
    언어 디자이너 에 게 도전 은 사용자 에 게 똑 같은 정 보 를 두 번 제공 하 라 고 요구 하 는 것 을 어떻게 피 하 는 것 입 니까?구조 함 수 를 옮 기 는 서명 에서 구성원 의 유형 을 옮 길 수 있 는(앞의 pair 전이 구조 함수 중의 5,6 줄)이 라 고 가리 키 는 동시에 구성원 초기 화 목록 에서 구성원 을 진정 으로 옮 겨 야 한다(10,11 줄).현재 토론 중인 가능성 은 새로운 속성 문법 을 사용 하여 vector 로 이동 하 는 구조 함 수 를 이렇게 쓸 수 있 도록 하 는 것 입 니 다.
    vector(vector&& rhs) [[moves(_alloc)]]
      : _begin( rhs._begin )
      , _end( rhs._end )
      , _cap( rhs._cap )
    {
        rhs._begin = rhs._end = rhs._cap = 0;
    }

    이 구 조 는 를 제외 하고 SFINAE 에 의 해 떨 어 질 것 이다.alloc 자 체 는 던 지지 않 은 전이 구조 함 수 를 가지 고 있 으 며,이 구성원 은 rhs 의 해당 구성원 에서 옮 겨 와 암시 적 으로 초기 화 됩 니 다.
    불 행 히 도 C++0x 의 속성 에 대한 역할 에 대해 약간의 차이 가 존재 하기 때문에 우 리 는 위원회 가 어떤 문법 을 받 아들 일 지 모 르 지만 적어도 우 리 는 원칙적으로 문제 가 어디 에 있 는 지,그리고 그것 을 어떻게 해결 하 는 지 이해 했다 고 생각한다.
    후속
    자,읽 어 주 셔 서 감사합니다.오늘 은 여기까지.다음 편 에 서 는 완벽 한 퍼 가기 에 대해 토론 할 것 입 니 다.그리고 우 리 는 당신 에 게 C+03 의 이전 시 뮬 레이 션 에 관 한 조 사 를 빚 진 것 도 잊 지 않 았 습 니 다.

    좋은 웹페이지 즐겨찾기