C ++ 11에서 유형을 이동 불가능하게 만드는 경우 않는다는 것에 놀랐습니다. 나는

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

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

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



답변

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여전히 움직일 수 있습니다복사본이 유효한 이동 작업이므로 언어 ​​용어에서. 그러나 복사 불가능한 유형의 경우 메모리 조각을 이동하고 싶지 않거나 이동할 수없고 해당 값도 복사 할 수없는 경우 이동할 수 없습니다. 뮤텍스 또는 원자 변수는 메모리의 특정 위치 (특수 속성으로 처리됨)이므로 이동이 의미가없고 복사도 불가능하므로 이동할 수 없습니다.


답변

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

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

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

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

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

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


답변

실제로 주변을 검색 할 때 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


답변

내가 찾은 또 다른 이유는 성능입니다. 값을 보유하는 클래스 ‘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 원칙).


답변