std :: move ()는 무엇이며 언제 사용해야합니까?


656
  1. 무엇입니까?
  2. 무엇을합니까?
  3. 언제 사용해야합니까?

좋은 링크를 부탁드립니다.


43
Bjarne Stroustrup 은 Rvalue References
DumbCoder


12
이 질문은 언급한다 std::move(T && t); 또한 존재 std::move(InputIt first, InputIt last, OutputIt d_first)관련된 알고리즘이다 std::copy. 나는 다른 사람들이 내가 처음 std::move세 가지 논쟁에 직면했을 때와 같이 혼란스럽지 않도록 지적합니다 . en.cppreference.com/w/cpp/algorithm/move
josaphatv

답변:


287

C ++ 11 R- 값 참조 및 이동 생성자에 대한 Wikipedia 페이지

  1. C ++ 11에서는 복사 생성자 외에도 객체에 이동 생성자가있을 수 있습니다.
    또한 복사 할당 연산자 외에도 이동 할당 연산자가 있습니다.
  2. 객체의 유형이 "rvalue-reference"( Type &&) 인 경우 이동 생성자 대신 이동 생성자가 사용됩니다 .
  3. std::move() 객체에 대한 rvalue-reference를 생성하여 객체에서 이동할 수 있도록하는 캐스트입니다.

복사본을 피하는 새로운 C ++ 방법입니다. 예를 들어 이동 생성자를 사용하면 a std::vector는 데이터에 대한 내부 포인터를 새 객체에 복사하여 이동 된 객체를 이동 된 상태로 유지하므로 모든 데이터를 복사하지는 않습니다. 이것은 C ++-유효합니다.

이동 의미, rvalue, 완벽한 전달을 위해 인터넷 검색을 시도하십시오.


40
이동 시맨틱은 이동 된 오브젝트가 유효한 상태를 유지 해야 하는데 이는 올바르지 않은 상태가 아닙니다. (Rationale : 그것은 여전히 ​​파괴되어야한다.)
GManNickG

13
@ GMan : 글쎄, 그것은 파괴하기에 안전한 상태에 있어야하지만, AFAIK는 다른 용도로 사용할 필요가 없습니다.
Zan Lynx

8
@ 잔 린스 : 맞습니다. 표준 라이브러리에는 추가로 이동 된 객체를 할당 할 수 있어야하지만 이는 일반적인 요구 사항이 아니라 stdlib에 사용 된 객체에만 해당됩니다.
GManNickG

25
-1 "std :: move ()는 이동 의미론을 사용하는 C ++ 11 방법" 입니다. std::move()이동 의미론을 사용하는 방법이 아니며, 이동 의미론은 프로그래머에게 투명하게 수행됩니다. move원래의 lvalue가 더 이상 사용되지 않는 한 지점에서 다른 지점으로 값을 전달하는 유일한 캐스트입니다.
Manu343726

15
나는 더 나아갈 것이다. std::move자체는 "아무것도하지 않습니다"-부작용이 없습니다. 단지 프로그래머가 해당 객체에 대해 더 이상 신경 쓰지 않는다는 신호를 컴파일러에 알립니다. 즉 , 소프트웨어의 다른 부분에 객체에서 이동할 수있는 권한 을 부여 하지만 이동할 필요는 없습니다. 실제로, rvalue 참조를받는 사람은 데이터로 할 일과 할 일에 대해 약속 할 필요가 없습니다.
Aaron McDaid

241

1. "무엇입니까?"

std::move() 기술적으로는 함수 이지만 실제로 는 함수 가 아니라고 말할 있습니다 . 컴파일러가 표현식의 값을 고려하는 방식 사이 의 변환기 입니다.

2. "무엇을합니까?"

가장 먼저 주목할 것은 std::move() 실제로 아무것도 움직이지 않는다는 것 입니다. 표현식을 이름이 지정된 변수와 같은 lvalue 에서 xvalue로 변환 합니다. xvalue는 컴파일러에게 다음을 알려줍니다.

당신은 저를 약탈하고, 내가 가지고있는 것을 옮기고 다른 곳에서 사용할 수 있습니다 (어쨌든 곧 파괴 될 것이기 때문에) ".

즉,를 사용할 때 std::move(x)컴파일러가 식인종을 허용합니다 x. 따라서 x메모리에 자체 버퍼가 있다면 std::move()컴파일러가 다른 객체를 소유 할 수 있습니다.

prvalue (예 : 임시로 전달되는) 에서 이동할 수도 있지만 거의 유용하지 않습니다.

3. "언제 사용해야합니까?"

이 질문을하는 또 다른 방법은 "기존 객체의 자원을 어떻게 잠식 할 수 있습니까?"입니다. 글쎄, 응용 프로그램 코드를 작성한다면 컴파일러가 만든 임시 객체를 많이 사용하지 않을 것입니다. 따라서 주로 생성자, 연산자 메서드, 표준 라이브러리 알고리즘과 같은 함수 등에서 객체를 자동으로 많이 생성하고 파괴하는 장소 에서이 작업을 수행합니다. 물론, 그것은 단지 경험의 법칙입니다.

일반적으로 복사 대신 한 개체에서 다른 개체로 리소스를 '이동'합니다. @Guillaume 은이 페이지에 링크되어 있으며 간단한 예제가 있습니다. 두 개의 객체를 적은 복사로 바꾸는 것입니다.

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we now have two copies of a
    a = b;      // we now have two copies of b (+ discarded a copy of a)
    b = tmp;    // we now have two copies of tmp (+ discarded a copy of b)
}

이동을 사용하면 자원을 복사하지 않고 교환 할 수 있습니다.

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

Tvector<int>를 들어 크기가 n 일 때 어떤 일이 발생하는지 생각해보십시오 . 첫 번째 버전에서는 3 * n 요소를 읽고 씁니다. 두 번째 버전에서는 기본적으로 벡터 버퍼에 대한 3 개의 포인터와 3 개의 버퍼 크기 만 읽고 씁니다. 물론, 수업 T은 어떻게 움직이는지를 알아야합니다. 이 클래스가 T작동하려면 클래스 에 이동 할당 연산자와 클래스 를 위한 이동 생성자가 있어야합니다.


3
오랫동안 이러한 이동 의미론을 들어 본 적이 없었습니다. 이 설명에서 당신은 그것을 딥 카피 대신 얕은 카피처럼 보입니다.
Zebrafish

7
@TitoneMaurice : 원래 값을 더 이상 사용할 수 없으므로 사본이 아니라는 점을 제외하고.
einpoklum

3
@ Zebrafish 당신은 더 잘못 될 수 없습니다. 얕은 복사본은 원본을 정확히 같은 상태로두고 이동하면 보통 원본이 비어 있거나 유효한 상태가됩니다.
rubenvb

16
@rubenvb Zebra가 완전히 잘못된 것은 아닙니다. 혼란스러운 오류를 피하기 위해 원래의 cannabilised 객체가 의도적으로 방해되는 것이 사실이지만 (예를 들어 nullptr에 포인터를 설정하여 더 이상 pointees를 소유하지 않는다는 신호를 보냅니다) 사실은 단순히 소스에서 포인터를 복사하여 전체 이동이 구현된다는 사실 대상에 대한 (그리고 의도적으로 포인트를 사용하여 피하는 것을 피하는 것) 실제로 얕은 사본을 연상시킵니다. 실제로, 나는 이동 얕은 사본 이라고 말하고 선택적으로 소스의 부분적인 자기 파괴 를한다고 말할 것이다 . (계속)
궤도의 가벼움 경주

3
(계속). (오히려 같은 나는) 우리가이 정의를 허용하는 경우, 제브라 피쉬의 관찰이되지 잘못, 단지 약간 불완전 다음 @.
궤도에서 가벼움 경주

145

복사하지 않고 객체의 내용을 다른 곳으로 "전송"해야 할 때 이동을 사용할 수 있습니다 (즉, 내용이 복제되지 않기 때문에 unique_ptr과 같이 복사 할 수없는 일부 객체에서 사용할 수 있습니다). std :: move를 사용하여 객체가 복사하지 않고 임시 객체의 내용을 가져와 많은 시간을 절약 할 수도 있습니다.

이 링크는 정말 도움이되었습니다.

http://thbecker.net/articles/rvalue_references/section_01.html

답변이 너무 늦어지면 죄송하지만 std :: move에 대한 좋은 링크를 찾고 있었으며 위의 링크가 "austere"보다 약간 컸습니다.

이것은 r- 값 참조에 중점을두고 있으며, 여기서는 컨텍스트를 사용해야하며 더 자세하다고 생각합니다.이 링크를 여기에 공유하고 싶었습니다.


26
좋은 링크. 나는 항상 wikipedia 기사와 내가 실제로 당신에게 사실을 던지기 때문에 혼란스러워했던 다른 링크를 발견했다. 생성자의 "시맨틱 이동"은 다소 분명하지만 &&-값을 전달하는 것에 대한 모든 세부 사항은 그렇지 않습니다. 따라서 튜토리얼 스타일 설명은 매우 훌륭했습니다.
Christian Stieber

66

Q : 무엇입니까 std::move?

A : std::move()rvalue 참조로 캐스트하기위한 C ++ 표준 라이브러리의 함수입니다.

간단 std::move(t)하게 다음과 같습니다.

static_cast<T&&>(t);

rvalue는 변수에 저장되지 않는 중간 함수 결과와 같이이를 정의하는 표현식을 넘어 지속되지 않는 임시 값입니다.

int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated

std :: move ()의 구현은 다음과 같이 N2027 : "Rvalue 참조에 대한 간략한 소개" 에 제공됩니다.

template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
    return a;
}

보시다시피 , 값 ( ), 참조 유형 ( ) 또는 rvalue 참조 ( )를 사용하여 호출하더라도 상관없이 std::move반환합니다 .T&&TT&T&&

Q : 무엇을합니까?

A : 캐스트로서 런타임 중에 아무것도하지 않습니다. 컴파일러에게 참조를 rvalue로 계속 고려하고 있다고 알리는 것은 컴파일 타임에만 관련이 있습니다.

foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)

int a = 3 * 5;
foo(a);     // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`

하지 않는 것 :

  • 논쟁의 사본을 만드십시오
  • 복사 생성자 호출
  • 인수 객체 변경

Q : 언제 사용해야합니까?

A : std::movervalue (임시 표현식)가 아닌 인수로 이동 의미를 지원하는 함수를 호출하려는 경우 사용해야 합니다.

이것은 다음과 같은 후속 질문을 제기합니다.

  • 이동 의미 란 무엇입니까? 복사 시맨틱과 대조적으로 시맨틱 이동은 오브젝트의 멤버가 다른 오브젝트의 멤버를 복사하는 대신 '취득'하여 초기화되는 프로그래밍 기술입니다. 이러한 '인계'는 포인터 및 리소스 핸들에서만 의미가 있으며, 기본 데이터가 아닌 포인터 또는 정수 핸들을 복사하여 저렴하게 전송할 수 있습니다.

  • 이동 시맨틱을 지원하는 클래스와 객체는 무엇입니까? 멤버를 복사하는 대신 멤버를 전송하는 것이 도움이되는 경우 자신의 클래스에서 이동 의미론을 구현하는 것은 개발자의 책임입니다. 이동 의미론을 구현하면 이동 의미론을 사용하여 클래스를 효율적으로 처리하는 데 대한 지원을 추가 한 많은 라이브러리 프로그래머의 작업으로부터 직접 혜택을 얻을 수 있습니다.

  • 왜 컴파일러가 스스로 알아낼 수 없습니까? 달리 말하지 않는 한 컴파일러는 함수의 다른 오버로드를 호출 할 수 없습니다. 컴파일러가 정규 또는 이동 버전의 함수를 호출해야하는지 선택하도록 도와야합니다.

  • 어떤 상황에서 컴파일러에게 변수를 rvalue로 취급해야한다고 말하고 싶습니까? 이는 중간 결과가 복구 될 수 있음을 알고있는 템플리트 또는 라이브러리 함수에서 발생합니다.


2
주석에 의미가있는 코드 예제의 경우 +1입니다. 다른 최고 답변은 "move"자체를 사용하여 std :: move를 정의합니다. 실제로 아무것도 명확히하지 않습니다! --- 인수의 사본을 만들지 않으면 원래 값을 안정적으로 사용할 수 없다는 것을 언급 할 가치가 있다고 생각합니다.
ty

34

std :: move 자체는 실제로 많은 작업을 수행하지 않습니다. 객체의 이동 생성자를 호출한다고 생각했지만 실제로 유형 변환을 수행합니다 (lvalue 변수를 rvalue로 캐스팅하여 해당 변수를 이동 생성자 또는 할당 연산자에 인수로 전달할 수 있음).

따라서 std :: move는 이동 의미론을 사용하는 선구자로 사용됩니다. 이동 의미론은 기본적으로 임시 객체를 처리하는 효율적인 방법입니다.

객체 고려 A = B + C + D + E + F;

이것은 멋진 코드이지만 E + F는 임시 객체를 생성합니다. 그런 다음 D + temp는 다른 임시 객체 등을 생성합니다. 클래스의 각 일반 "+"연산자에서 딥 카피가 발생합니다.

예를 들어

Object Object::operator+ (const Object& rhs) {
    Object temp (*this);
    // logic for adding
    return temp;
}

이 함수에서 임시 객체를 생성하는 것은 쓸모가 없습니다. 이러한 임시 객체는 범위를 벗어나면 줄 끝에서 삭제됩니다.

오히려 이동 의미론을 사용하여 임시 객체를 "약탈"하고

 Object& Object::operator+ (Object&& rhs) {
     // logic to modify rhs directly
     return rhs;
 }

불필요하게 깊은 사본이 만들어지지 않도록합니다. 예를 참조하면 딥 카피가 발생하는 유일한 부분은 이제 E + F입니다. 나머지는 이동 의미론을 사용합니다. 결과를 A에 할당하려면 이동 생성자 또는 할당 연산자도 구현해야합니다.


3
당신은 이동 의미론에 대해 이야기했습니다. 질문에 대해 std :: move를 사용할 수있는 방법으로 답변에 추가해야합니다.
Koushik Shetty 2016 년

2
@Koushik std :: move는 많은 기능을 수행하지 않지만 이동 의미론을 구현하는 데 사용됩니다. std :: move에 대해 모른다면 아마도 이동 의미도 모릅니다
user929404

1
"많은 일이 없다"(예 : rvalue 참조에 대한 static_cast). 실제로 수행하는 작업과 수행하는 작업은 OP가 요청한 작업입니다. std :: move가 어떻게 작동하는지 알 필요는 없지만 이동 의미론이 무엇을하는지 알아야합니다. 또한 "이동 의미론을 구현하는 데 사용됩니다." 이동 의미를 알고 std :: move를 이해합니다. 이동은 운동에 도움이되고 이동 시맨틱을 사용합니다. std :: move는 인수를 rvalue 참조로 변환하는 것 외에는 이동 의미론에 필요한 것입니다.
Koushik Shetty 2016 년

10
"그러나 E + F는 임시 객체를 생성합니다"-연산자 +는 오른쪽에서 왼쪽이 아니라 왼쪽에서 오른쪽으로갑니다. 따라서 B+C첫 번째 것입니다!
Ajay

8

"무엇입니까?" 그리고 "그것은 무엇을합니까?" 위에서 설명했습니다.

"사용해야 할 때"에 대한 예를 들겠습니다.

예를 들어, 큰 배열과 같은 많은 리소스가있는 클래스가 있습니다.

class ResHeavy{ //  ResHeavy means heavy resource
    public:
        ResHeavy(int len=10):_upInt(new int[len]),_len(len){
            cout<<"default ctor"<<endl;
        }

        ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
            cout<<"copy ctor"<<endl;
        }

        ResHeavy& operator=(const ResHeavy& rhs){
            _upInt.reset(new int[rhs._len]);
            _len = rhs._len;
            cout<<"operator= ctor"<<endl;
        }

        ResHeavy(ResHeavy&& rhs){
            _upInt = std::move(rhs._upInt);
            _len = rhs._len;
            rhs._len = 0;
            cout<<"move ctor"<<endl;
        }

    // check array valid
    bool is_up_valid(){
        return _upInt != nullptr;
    }

    private:
        std::unique_ptr<int[]> _upInt; // heavy array resource
        int _len; // length of int array
};

테스트 코드 :

void test_std_move2(){
    ResHeavy rh; // only one int[]
    // operator rh

    // after some operator of rh, it becomes no-use
    // transform it to other object
    ResHeavy rh2 = std::move(rh); // rh becomes invalid

    // show rh, rh2 it valid
    if(rh.is_up_valid())
        cout<<"rh valid"<<endl;
    else
        cout<<"rh invalid"<<endl;

    if(rh2.is_up_valid())
        cout<<"rh2 valid"<<endl;
    else
        cout<<"rh2 invalid"<<endl;

    // new ResHeavy object, created by copy ctor
    ResHeavy rh3(rh2);  // two copy of int[]

    if(rh3.is_up_valid())
        cout<<"rh3 valid"<<endl;
    else
        cout<<"rh3 invalid"<<endl;
}

아래와 같이 출력 :

default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid

우리는 것을 알 수 있습니다 std::move으로 move constructor만든다 쉽게 자원을 변환.

다른 std::move유용한 곳이 있습니까?

std::move요소 배열을 정렬 할 때도 유용 할 수 있습니다. 선택 정렬 및 버블 정렬과 같은 많은 정렬 알고리즘은 요소 쌍을 교체하여 작동합니다. 이전에는 스와핑을 수행하기 위해 카피 시맨틱에 의지해야했습니다. 이제보다 효율적인 이동 의미론을 사용할 수 있습니다.

하나의 스마트 포인터로 관리되는 컨텐츠를 다른 스마트 포인터로 옮기려는 경우에도 유용 할 수 있습니다.

인용 :


0

다음은 (간단한) 사용자 정의 벡터에 std :: move를 사용하는 전체 예입니다.

예상 출력 :

 c: [10][11]
 copy ctor called
 copy of c: [10][11]
 move ctor called
 moved c: [10][11]

다음과 같이 컴파일하십시오.

  g++ -std=c++2a -O2 -Wall -pedantic foo.cpp

암호:

#include <iostream>
#include <algorithm>

template<class T> class MyVector {
private:
    T *data;
    size_t maxlen;
    size_t currlen;
public:
    MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
    MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }

    MyVector<T> (const MyVector& o) {
        std::cout << "copy ctor called" << std::endl;
        data = new T [o.maxlen];
        maxlen = o.maxlen;
        currlen = o.currlen;
        std::copy(o.data, o.data + o.maxlen, data);
    }

    MyVector<T> (const MyVector<T>&& o) {
        std::cout << "move ctor called" << std::endl;
        data = o.data;
        maxlen = o.maxlen;
        currlen = o.currlen;
    }

    void push_back (const T& i) {
        if (currlen >= maxlen) {
            maxlen *= 2;
            auto newdata = new T [maxlen];
            std::copy(data, data + currlen, newdata);
            if (data) {
                delete[] data;
            }
            data = newdata;
        }
        data[currlen++] = i;
    }

    friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
        auto s = o.data;
        auto e = o.data + o.currlen;;
        while (s < e) {
            os << "[" << *s << "]";
            s++;
        }
        return os;
    }
};

int main() {
    auto c = new MyVector<int>(1);
    c->push_back(10);
    c->push_back(11);
    std::cout << "c: " << *c << std::endl;
    auto d = *c;
    std::cout << "copy of c: " << d << std::endl;
    auto e = std::move(*c);
    delete c;
    std::cout << "moved c: " << e << std::endl;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.