본문으로 바로가기

LR Value의 이해 - ③ std::move를 활용한 최적화

함수 인자로 전달

R-Value는 한 번 사용되고 사라진다는 특징이 있다. 그래서 R-Value로 인자를 전달받을 경우 값을 복사하지 말고 소유권을 이전받는 방식이 더욱 효율적이다. 아래의 3가지 함수를 살펴보자.

#include <string>

// value로 전달
void storeByValue(std::string s)
{
    std::string b = s;
}
// L-Ref로 전달
void storeByLRef(std::string& s)
{
    std::string b = s;
}
// R-Ref로 전달
void storeByRRef(std::string&& s)
{
    std::string b = std::move(s);
}

int main()
{
    std::string a = "abc";
    storeByValue(a);
    storeByLRef(a);
    storeByRRef(std::move(a));
    return 0;
}

위 3가지 함수를 그림으로 표현하면 다음과 같다.

  • storeByValue: 두번 copy 됨
  • sotreByLRef: 한번 copy 됨
  • sotreByRRef: copy가 없음. 대신 a의 소유권이 b로 이전되기 때문에 main 함수의 a는 nullptr이 됨.

std::move를 활용한 최적화

앞서 다룬 storeByRRef 함수는 0 copy이기 때문에 성능은 좋더라도 main 함수의 a는 값을 잃어버리는 문제가 발생한다. 여기서 구현하고자 하는 것은 다음과 같다.

  • 인자가 L-Value 일 경우 원본을 유지하고, 원본을 유지하기 위해서 1 copy
  • 인자가 R-Value일 경우 원본을 유지할 필요가 없기 때문에 0 copy

최적화를 위해서 몇 가지 함수를 살펴보자.

Pass by value

class Cat {
public:
    void setName(std::string s)
    {
        name = s;
    }
private:
    std::string name;
};
int main()
{
    Cat kitty;
    std::string str = "kitty";

    // L-value 전달 : 2 copy
    kitty.setName(str);

    // R-value 전달 : 1 copy
    kitty.setName("kitty");
    return 0;
}

만약 위와 같이 매개변수로 R-Value를 넘겨줄 경우 컴파일러는 copy를 하지 않고 바로 s에 kitty를 넣어주는 copy elision 최적화를 진행한다. copy elision 최적화란 컴파일러는 setName의 인자가 R-Value인것을 알기 때문에 copy가 아닌 move로 s에 값을 넣어주는 최적화를 뜻한다. 그래서 name에 s를 대입하는 상황에서 한 번의 copy만 발생한다. 그러나 L-value를 전달 할 때는 copy가 불필요하게 2번이나 발생한다. 아래의 코드를 살펴보자.

Pass by Ref

class Cat {
public:
    void setName(std::string& s)
    {
        name = s;
    }
private:
    std::string name;
};
int main()
{
    Cat kitty;
    std::string str = "kitty";

    // L-value 전달 : 1 copy
    kitty.setName(str);

    // R-value 전달 : 에러 발생
    kitty.setName("kitty"); 
    return 0;
}

위 코드는 매개변수로 반드시 참조 가능한 L-Value를 넘겨줘야 하기 때문에 R-Value를 전달할 경우 컴파일 에러가 발생한다.

Pass by Value 그리고 std::move

앞서 나온 Pass by value에서 값을 대입할 때 std::move를 사용하는 방법이다.

class Cat {
public:
    void setName(std::string s)
    {
        name = std::move(s);
    }
private:
    std::string name;
};
int main()
{
    Cat kitty;
    std::string str = "kitty";

    // L-value 전달 : 1 copy
    kitty.setName(str);

    // R-value 전달 : 0 copy
    kitty.setName("kitty");
    return 0;
}

함수 인자로 L-value로 전달할 경우 매개변수 sstr값이 복사되면서 한번 복사가 진행된다. 그리고 s의 값을 name 이전시켜주게 되면 단 한 번의 copy로 값을 넘겨받을 수 있다.

 

그리고 함수 인자로 R-value를 전달할 경우 copy elision 최적화로 인해서 s는 copy 없이 값을 받을 수 있고 move를 통해서 또다시 copy 없이 name에 값을 넘겨줄 수 있다.

 

그리고 앞서 문제 되었던 main함수의 str 데이터 또한 문제없이 보존할 수 있다.

 


출처: https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg