C ++ 11에서 유형을 이동 불가능하게 만드는 경우


127

나는 이것이 내 검색 결과에 나타나지 않는다는 것에 놀랐습니다. 나는 C ++ 11에서 이동 의미론의 유용성을 고려할 때 누군가 전에 이것을 물었을 것이라고 생각했습니다.

언제 C ++ 11에서 클래스를 이동 불가능하게 만들어야합니까 (또는 좋은 생각입니까)?

(이유는 다른 것입니다 기존 코드와의 호환성 문제보다.)


2
부스트는 한 발 앞서 항상 - "이동 유형 비싼"( boost.org/doc/libs/1_48_0/doc/html/container/move_emplace.html )
SChepurin

1
나는 이것이 +1Herb (또는 그의 쌍둥이 처럼 보이는 것처럼 ) 의 매우 철저한 답변을 가진 매우 좋고 유용한 질문이라고 생각합니다 . 그래서 나는 그것을 FAQ 항목으로 만들었습니다. 누군가 가 라운지 에서 저를 핑하는 것에 반대한다면 그곳에서 논의 할 수 있습니다.
sbi

1
AFAIK 이동 가능 클래스는 여전히 슬라이싱의 대상이 될 수 있으므로 모든 다형성 기본 클래스 (즉, 가상 함수가있는 모든 기본 클래스)에 대해 이동 (및 복사)을 금지하는 것이 좋습니다.
Philipp

1
@Mehrdad : "T에는 이동 생성자가 있습니다"와 " T x = std::move(anotherT);합법적 인"은 동등하지 않다는 것입니다. 후자는 T에 이동 ctor가없는 경우 복사 ctor에서 폴백 할 수있는 이동 요청입니다. 그렇다면 "이동식"이란 정확히 무엇을 의미합니까?
sellibitze

1
@Mehrdad : "MoveConstructible"의 의미에 대한 C ++ 표준 라이브러리 섹션을 확인하십시오. 일부 반복기에는 이동 생성자가 없을 수 있지만 여전히 MoveConstructible입니다. 사람들이 염두에두고있는 "움직이는"사람들의 다양한 정의에주의하십시오.
sellibitze 2013 년

답변:


110

Herb의 답변 (편집되기 전)은 실제로 움직일 수 없는 유형의 좋은 예를 제공했습니다 std::mutex..

OS의 기본 뮤텍스 유형 (예 : pthread_mutex_tPOSIX 플랫폼에서)은 객체 주소가 값의 일부임을 의미하는 "위치 불변"이 아닐 수 있습니다. 예를 들어 OS는 초기화 된 모든 뮤텍스 개체에 대한 포인터 목록을 유지할 수 있습니다. std::mutex데이터 멤버로 기본 OS 뮤텍스 유형 이 포함되어 있고 기본 유형의 주소가 고정되어 있어야하는 경우 (OS가 해당 뮤텍스에 대한 포인터 목록을 유지하기 때문에) std::mutex네이티브 뮤텍스 유형을 힙에 저장해야합니다. std::mutex개체 사이를 이동할 때 동일한 위치 또는 std::mutex이동해서는 안됩니다. 힙에 저장하는 것은 불가능합니다. 왜냐하면 std::mutex에는 constexpr생성자가 있고 상수 초기화 (예 : 정적 초기화)에 적합해야 하기 때문 입니다.std::mutex프로그램의 실행이 시작되기 전에 생성되도록 보장되므로 생성자는 new. 따라서 남은 유일한 옵션은 std::mutex움직이지 않는 것입니다.

고정 주소가 필요한 것을 포함하는 다른 유형에도 동일한 추론이 적용됩니다. 자원의 주소가 고정되어 있어야한다면 이동하지 마십시오!

움직이지 않는 것에 대한 또 다른 주장이 있는데 std::mutex, 그것은 안전하게하는 것이 매우 어렵다는 것입니다. 뮤텍스가 움직이는 순간 아무도 뮤텍스를 잠그려고하지 않는다는 것을 알아야하기 때문입니다. 뮤텍스는 데이터 레이스를 방지하는 데 사용할 수있는 빌딩 블록 중 하나이기 때문에 레이스 자체에 대해 안전하지 않다면 불행 할 것입니다! 움직일 std::mutex수 없는 것은 일단 생성되고 파괴되기 전에 누구나 할 수있는 유일한 일은 그것을 잠그고 잠금을 해제하는 것이며, 이러한 작업은 명시 적으로 스레드 안전이 보장되고 데이터 경합을 유발하지 않습니다. 이 동일한 인수가 std::atomic<T>객체에 적용됩니다 . 원자 적으로 이동할 수 없으면 안전하게 이동할 수 없으며 다른 스레드가 호출을 시도 할 수 있습니다.compare_exchange_strong움직이고있는 순간에 물체에 따라서 유형을 이동할 수 없어야하는 또 다른 경우는 안전한 동시 코드의 저수준 빌딩 블록이고 모든 작업의 ​​원 자성을 보장해야하는 경우입니다. 객체 값이 언제든지 새 객체로 이동 될 수있는 경우 모든 원자 변수를 보호하기 위해 원자 변수를 사용하여 사용하는 것이 안전한지 또는 이동되었는지 알 수 있도록 원자 변수를 사용해야합니다. 그 원자 변수 등등 ...

나는 객체가 값의 보유자 역할을하는 유형이나 값의 추상화 역할을하는 유형이 아닌 순수한 메모리 조각 일 때 그것을 이동하는 것은 의미가 없다고 말하는 것으로 일반화 할 것이라고 생각합니다. int이동할 수없는 것과 같은 기본 유형 : 이동은 복사 일뿐입니다. 에서 내장을 뜯어 낼 수없고 int, 그 값을 복사 한 다음 0으로 설정할 수 있지만, 여전히 int값이 있고 메모리의 바이트 일뿐입니다. 하지만 int여전히 움직일 수 있습니다복사본이 유효한 이동 작업이므로 언어 ​​용어에서. 그러나 복사 불가능한 유형의 경우 메모리 조각을 이동하고 싶지 않거나 이동할 수없고 해당 값도 복사 할 수없는 경우 이동할 수 없습니다. 뮤텍스 또는 원자 변수는 메모리의 특정 위치 (특수 속성으로 처리됨)이므로 이동이 의미가없고 복사도 불가능하므로 이동할 수 없습니다.


17
+1 특별한 주소가 있기 때문에 이동할 수없는 무언가의 덜 이국적인 예는 방향성 그래프 구조의 노드입니다.
Potatoswatter 2013 년

3
뮤텍스가 복사 할 수없고 이동할 수없는 경우 뮤텍스를 포함하는 객체를 어떻게 복사하거나 이동할 수 있습니까? (그것 스레드 안전 클래스처럼 ... 동기화를위한 자신의 뮤텍스의)
tr3w

4
당신은 힙에 뮤텍스를 생성하지 않는 @ tr3w, 당신은 unique_ptr 또는 유사한을 통해 물을 수 없습니다
조나단 Wakely

2
@ tr3w : 뮤텍스 부분을 제외한 전체 클래스를 이동하지 않겠습니까 ?
user541686 2013-01-13

3
@BenVoigt, 그러나 새 개체에는 자체 뮤텍스가 있습니다. 나는 그가 뮤텍스 멤버를 제외한 모든 멤버를 이동시키는 사용자 정의 이동 작업을 의미한다고 생각합니다. 그렇다면 오래된 개체가 만료되면 어떻게 될까요? 뮤텍스는 함께 만료됩니다.
Jonathan Wakely 2013 년

57

짧은 대답 : 유형이 복사 가능한 경우 이동 가능해야합니다. 그러나 그 반대는 사실이 아닙니다. 같은 일부 유형 std::unique_ptr은 이동할 수 있지만 복사하는 것은 의미가 없습니다. 이들은 자연스럽게 이동 전용 유형입니다.

약간 더 긴 대답이 이어집니다 ...

두 가지 유형의 주요 유형이 있습니다 (특성과 같은 다른 특수 목적 유형 중에서).

  1. 가치처럼 같은 종류의, int또는 vector<widget>. 이는 값을 나타내며 자연스럽게 복사 가능해야합니다. C ++ 11에서는 일반적으로 이동을 복사의 최적화로 생각해야합니다. 따라서 모든 복사 가능한 유형은 자연스럽게 이동 가능해야합니다. 이동은 자주 사용하지 않는 복사를 수행하는 효율적인 방법 일뿐입니다. 더 이상 원래 개체가 필요하고 어쨌든 그것을 파괴 할 것입니다.

  2. 가상 또는 보호 된 멤버 함수가있는 기본 클래스 및 클래스와 같이 상속 계층 구조에 존재하는 참조와 유사한 형식입니다. 이들은 일반적으로 포인터 또는 참조 (종종 a base*또는)에 의해 유지 base&되므로 슬라이스를 방지하기 위해 복사 구성을 제공하지 않습니다. 기존 객체와 같은 다른 객체를 얻으려면 일반적으로 clone. 두 가지 이유로 이동 구성이나 할당이 필요하지 않습니다. 복사 할 수없고 이미 훨씬 더 효율적인 자연스러운 "이동"작업이 있습니다. 개체에 대한 포인터를 복사 / 이동하기 만하면 개체 자체는 그렇지 않습니다. 새로운 메모리 위치로 이동해야합니다.

대부분의 유형은이 두 범주 중 하나에 속하지만 유용한 다른 유형도 있습니다. 특히 여기서와 같이 리소스의 고유 한 소유권을 표현하는 std::unique_ptr유형은 가치와 유사하지 않지만 (복사하는 것이 타당하지 않음) 직접 사용 (항상 그런 것은 아님)하기 때문에 자연스럽게 이동 전용 유형입니다. 포인터 또는 참조로) 따라서이 유형의 개체를 한 위치에서 다른 위치로 이동하려고합니다.


61
겠습니까 실제 허브 셔터는 일어서주십시오? :)
fredoverflow

6
예, 하나의 OAuth Google 계정을 다른 계정으로 전환했으며 여기에 제공되는 두 개의 로그인을 병합하는 방법을 찾느 라 번거로울 수 없습니다. (훨씬 더 매력적인 것들 중에서 OAuth에 대한 또 다른 주장입니다.) 아마 다시는 다른 것을 사용하지 않을 것입니다. 그래서 지금은 가끔 SO 포스트에 사용할 것입니다.
Herb Sutter 2013 년

7
std::mutexPOSIX 뮤텍스가 주소로 사용되기 때문에 나는 그것이 움직일 수 없다고 생각했습니다 .
Puppy

9
@SChepurin : 사실은 HerbOverflow라고합니다.
sbi

26
이것은 많은 찬성 투표를 받고 있으며, 유형이 이동 전용이어야 할 때라고 말하는 사람이 아무도 없었습니다. :)
Jonathan Wakely 2013 년

18

실제로 주변을 검색 할 때 C ++ 11의 일부 유형이 움직일 수 없음을 발견했습니다.

  • 모든 mutex종류의 ( recursive_mutex, timed_mutex, recursive_timed_mutex,
  • condition_variable
  • type_info
  • error_category
  • locale::facet
  • random_device
  • seed_seq
  • ios_base
  • basic_istream<charT,traits>::sentry
  • basic_ostream<charT,traits>::sentry
  • 모든 atomic유형
  • once_flag

분명히 Clang에 대한 토론이 있습니다 : https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c++/pCO1Qqb3Xa4


1
... 반복자는 움직일 수 없어야합니까?! 뭐? 왜?
user541686

예, iterators / iterator adaptorsC ++ 11에 move_iterator가 있으므로 편집해야 한다고 생각 합니다.
billz

좋아 지금은 혼란스러워. 대상 을 이동하는 반복자에 대해 이야기 하고 있습니까? 아니면 반복자 자체를 이동시키는 것에 대해 이야기 하고 있습니까?
user541686

1
그래서입니다 std::reference_wrapper. 좋아, 나머지는 실제로 움직일 수없는 것 같다.
Christian Rau

1
이 세 가지 범주로 분류하는 것 : 1. 낮은 수준의 동시성 관련 유형 (아토, 뮤텍스), 2. 다형성 기본 클래스 ( ios_base, type_info, facet) 3. 분류 된 이상한 물건 ( sentry). 아마도 일반 프로그래머가 작성하는 이동 불가능한 클래스는 두 번째 범주에있을 것입니다.
Philipp

0

내가 찾은 또 다른 이유는 성능입니다. 값을 보유하는 클래스 'a'가 있다고 가정하십시오. 사용자가 제한된 시간 동안 (범위에 대해) 값을 변경할 수있는 인터페이스를 출력하려고합니다.

이를 달성하는 방법은 다음과 같이 소멸자에 값을 다시 설정하는 'a'에서 'scope guard'객체를 반환하는 것입니다.

class a 
{ 
    int value = 0;

  public:

    struct change_value_guard 
    { 
        friend a;
      private:
        change_value_guard(a& owner, int value) 
            : owner{ owner } 
        { 
            owner.value = value;
        }
        change_value_guard(change_value_guard&&) = delete;
        change_value_guard(const change_value_guard&) = delete;
      public:
        ~change_value_guard()
        {
            owner.value = 0;
        }
      private:
        a& owner;
    };

    change_value_guard changeValue(int newValue)
    { 
        return{ *this, newValue };
    }
};

int main()
{
    a a;
    {
        auto guard = a.changeValue(2);
    }
}

change_value_guard를 이동 가능하게 만들면 가드가 이동되었는지 확인하는 소멸자에 'if'를 추가해야합니다. 이는 추가 if 및 성능 영향입니다.

예, 물론 정상적인 최적화 프로그램에 의해 최적화 될 수 있지만 언어 (이동할 수없는 유형을 반환하려면 보장 된 복사 제거가 필요하지만 C ++ 17이 필요합니다)에 우리가 필요하지 않다는 것이 좋습니다. 생성 함수에서 반환하는 것 외에는 가드를 이동하지 않을 경우 지불합니다 (사용하지 않는 것에 대해 dont-pay-for-what-you-dont-use 원칙).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.