`const` 객체에`std :: move`를 사용할 수있는 이유는 무엇입니까?


113

C ++ 11에서는 다음 코드를 작성할 수 있습니다.

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

를 호출 std::move하면 개체를 이동하고 싶다는 의미입니다. 즉, 개체를 변경하겠습니다. const개체 를 이동하는 것은 비합리적인데 왜이 std::move동작을 제한 하지 않습니까? 장래에 함정이 되겠죠?

여기에 트랩은 Brandon이 주석에서 언급했듯이 의미합니다.

"내 생각에 그가 몰래 은밀하게"덫을 놓는다 "는 뜻이라고 생각한다. 왜냐하면 그가 깨닫지 못하면 의도 한 것과 다른 사본으로 끝나기 때문이다."

Scott Meyers의 'Effective Modern C ++'책에서 그는 다음과 같은 예를 제공합니다.

class Annotation {
public:
    explicit Annotation(const std::string text)
     : value(std::move(text)) //here we want to call string(string&&),
                              //but because text is const, 
                              //the return type of std::move(text) is const std::string&&
                              //so we actually called string(const string&)
                              //it is a bug which is very hard to find out
private:
    std::string value;
};

물체 std::move에 대한 작동이 금지되어 있다면 const버그를 쉽게 찾을 수 있었죠?


2
그러나 그것을 움직이십시오. 상태를 변경하십시오. std::move그 자체로는 객체에 아무것도하지 않습니다. 하나는 std::move이름이 잘못 되었다고 주장 할 수 있습니다.
juanchopanza

3
실제로 아무것도 움직이지 않습니다 . 이것이하는 일은 rvalue 참조로 캐스팅하는 것입니다. try CAT cat2 = std::move(cat);, CAT정규 이동 할당을 지원 한다고 가정 합니다.
WhozCraig

11
std::move다만 캐스트, 실제로는 아무것도 움직이지 않고있다
적색 경보

2
@WhozCraig : 게시 한 코드가 경고없이 컴파일 및 실행되므로 오해의 소지가 있습니다.
Mooing Duck

1
@MooingDuck 컴파일하지 않을 것이라고 결코 말하지 않았습니다. 기본 복사기가 활성화 된 경우에만 작동합니다. 그것을 꽉 쥐면 바퀴가 떨어집니다.
WhozCraig

답변:


55
struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

여기서 우리 std::moveT const. 그것은을 반환합니다 T const&&. strange정확히이 유형을 취하는 이동 생성자가 있습니다.

그리고 그것은 불린다.

이제이 이상한 유형이 제안이 수정할 버그보다 더 드물다는 것은 사실입니다.

그러나, 다른 한편으로는, 기존의 std::move사용자가 작업중인 유형이 있는지 알고하지 않는 일반적인 코드의 작품 더 나은 T또는를 T const.


3
당신은 왜 실제로 시도가 설명하는 첫 번째 대답 것에 대해 +1 원하는 전화 std::moveA의 const객체입니다.
Chris Drew

+1을받는 함수를 표시합니다 const T&&. 이것은 "나는 rvalue-ref를 사용하지만 수정하지 않겠다고 약속합니다"라는 종류의 "API 프로토콜"을 표현합니다. 나는 mutable을 사용할 때를 제외하고는 드문 일이라고 생각합니다. 또 다른 사용 사례는 forward_as_tuple거의 모든 것에 사용할 수 있고 나중에 사용할 수있는 것입니다.
F Pereira

107

여기에 간과하는 트릭이 있습니다. 즉, std::move(cat) 실제로 아무것도 움직이지 않는 것 입니다. 단지 컴파일러 에게 이동 을 시도 하도록 지시합니다 . 그러나 클래스에를 허용하는 생성자가 없으므로 const CAT&&대신 암시 적 const CAT&복사 생성자를 사용하고 안전하게 복사합니다. 위험도, 함정도 없습니다. 어떤 이유로 든 복사 생성자가 비활성화 된 경우 컴파일러 오류가 발생합니다.

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

인쇄 COPY가 아니라 MOVE.

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

언급 한 코드의 버그 는 안정성 문제 가 아니라 성능 문제 이므로 이러한 버그로 인해 충돌이 발생하지 않습니다. 더 느린 복사본을 사용합니다. 또한 이러한 버그는 이동 생성자가없는 상수가 아닌 개체에 대해서도 발생하므로 단순히 오버로드를 추가한다고해서 모든 개체가 포착되지는 않습니다. 매개 변수 유형에서 구성을 이동하거나 할당을 이동하는 기능을 확인할 수 있지만 복사 생성자에서 폴백 해야하는 일반 템플릿 코드를 방해 합니다. 그리고 도대체 누군가가에서 건설 할 수 있기를 원할 수도 있습니다. 그가 할 수 없다고 말하는 사람은 누구입니까?constconst CAT&&


업틱. 일반 이동 생성자 또는 할당 연산자의 사용자 정의 정의에 따라 복제 자의 암시 적 삭제는 손상된 컴파일을 통해이를 입증 할 것입니다. 좋은 대답입니다.
WhozCraig

또한 non- constlvalue 를 필요로하는 copy-constructor 도 도움이되지 않는다는 것을 언급 할 가치가 있습니다. [class.copy] §8 : "그렇지 않으면 암시 적으로 선언 된 복사 생성자는 X::X(X&)" 형식을 갖게됩니다 .
Deduplicator

9
나는 그가 컴퓨터 / 어셈블리 용어에서 "함정"을 의미한다고 생각하지 않는다. 나는 그가 그것을 "가리다"라는 뜻이라고 생각한다. 왜냐하면 그가 깨닫지 못하면 그가 의도 한 것이 아닌 사본으로 끝나기 때문이다. 그런 것 같아요 ..
브랜든

당신은 1 개의 찬성표를 더 얻고, 저는 4 개의 골드 배지를 더받을 수 있습니다. ;)
Yakk-Adam Nevraumont 2015

해당 코드 샘플에서 하이 파이브. 요점을 정말 잘 이해합니다.
Brent

20

지금까지 나머지 답변이 간과 된 한 가지 이유는 일반 코드가 이동시 탄력적 일 수 있기 때문입니다 . 예를 들어 동일한 값을 가진 다른 종류의 컨테이너를 만들기 위해 한 종류의 컨테이너에서 모든 요소를 ​​이동하는 일반 함수를 작성하고 싶다고 가정 해 보겠습니다.

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

멋지다, 이제는 상대적으로 효율적으로 a vector<string>를 만들 수 deque<string>있고 각 개인 string이 그 과정에서 움직일 것입니다.

하지만에서 이동하려면 어떻게해야 map합니까?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

경우 std::move비 주장 const인수의 위의 인스턴스는 move_each이동하려고하기 때문에 컴파일되지 않을 것이라고 const int합니다 ( key_typemap). 하지만이 코드는 상관하지 않는다 는 이동할 수없는 경우 key_type. 성능상의 이유로 mapped_type( std::string) 를 이동하려고합니다 .

그것은이 예를 들어, 그리고 일반 그 코딩의 그것과 같은 수많은 예를 들면 std::moveA는 이동 요청 이 아니라 이동하는 수요.


2

나는 OP와 같은 관심사를 가지고 있습니다.

std :: move는 개체를 이동하지 않으며 개체가 이동 가능함을 보장하지도 않습니다. 그럼 왜 무브라고 부르나요?

움직일 수없는 것은 다음 두 가지 시나리오 중 하나 일 수 있다고 생각합니다.

1. 이동 유형은 const입니다.

언어에 const 키워드가있는 이유는 컴파일러가 const로 정의 된 객체에 대한 변경을 방지하기를 원하기 때문입니다. Scott Meyers의 책에서 예를 들면 다음과 같습니다.

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     {  } // doesn't do what it seems to!    
     
    private:
     std::string value;
    };

문자 그대로 무엇을 의미합니까? const 문자열을 값 멤버로 이동하십시오. 적어도 설명을 읽기 전에 이해합니다.

언어가 이동을하지 않거나 std :: move ()가 호출 될 때 이동이 적용 가능하다는 것을 보장하지 않으려는 경우 단어 이동을 사용할 때 문자 그대로 오해의 소지가 있습니다.

언어가 std :: move를 사용하는 사람들이 더 나은 효율성을 갖도록 장려한다면, 특히 이런 유형의 명백한 문자 모순에 대해 가능한 한 빨리 이와 같은 함정을 방지해야합니다.

나는 사람들이 상수 이동이 불가능하다는 것을 알고 있어야한다는 데 동의하지만,이 의무는 명백한 모순이 발생할 때 컴파일러가 침묵 할 수 있음을 의미해서는 안됩니다.

2. 객체에 이동 생성자가 없습니다.

개인적으로 이것은 Chris Drew가 말했듯이 OP의 우려와는 별개의 이야기라고 생각합니다.

@hvd 그것은 나에게 약간의 논증처럼 보입니다. OP의 제안이 세상의 모든 버그를 수정하지 않는다고해서 반드시 그것이 나쁜 생각이라는 것을 의미하지는 않습니다 (아마도 그럴 것이지만 당신이주는 이유 때문은 아닙니다). – 크리스 드류


1

아무도 이것의 이전 버전과의 호환성 측면을 언급하지 않은 것에 놀랐습니다. 저는 std::move의도적으로 C ++ 11에서이를 수행하도록 설계 되었다고 생각 합니다. C ++ 98 라이브러리에 크게 의존하는 레거시 코드베이스로 작업하고 있으므로 복사 할당에 대한 폴백없이 이동하면 문제가 발생한다고 상상해보십시오.


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