태그 보관물: c++-faq

c++-faq

std :: move ()는 무엇이며 언제 사용해야합니까? 사용해야합니까? 좋은 링크를 부탁드립니다.

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

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



답변

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

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

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

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


답변

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작동하려면 클래스 에 이동 할당 연산자와 클래스 를 위한 이동 생성자가 있어야합니다.


답변

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

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

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

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

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


답변

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로 취급해야한다고 말하고 싶습니까? 이는 중간 결과가 복구 될 수 있음을 알고있는 템플리트 또는 라이브러리 함수에서 발생합니다.


답변

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에 할당하려면 이동 생성자 또는 할당 연산자도 구현해야합니다.


답변

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

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

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

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

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

인용 :


답변

다음은 (간단한) 사용자 정의 벡터에 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;
}