C++ - move constructor

mohadang·2022년 10월 14일
0

C++

목록 보기
28/48
post-thumbnail

값 분류

  • rvalue
  • lvalue

lvalue

  • 임시적이지 않은것...

  • 이름이 있는 것, 변수 있는 것 ..

  • 단일 식을 넘어 지속되는 개체

    • 주소가 있음
    • 이름이 있는 변수
    • const 변수
    • 배열 변수
    • 비트 필드(bit field)
    • 공용 구조체(unions)
    • 클래스 멤버
    • 좌측값 참조(&)로 반환하는 함수 호출
    • 문자열 리터럴
  • 결국 지금까지 봐온 많은 것들

  • Ex)

int number = 10;// number 는 lvalue
const int NAME_MAX = 20;// NAME_MAX 는 lvalue
int* numberPtr = &number;// numberPtr 는 lvalue

std::map<std::string, float> scoreMap;
scoreMap["Lulu"] = 60.f;// scoreMap 는 lvalue

struct Person
{
  std::string Name;
  int Age;
}

int main()
{
  Person person;
  person.Name = "Lulu";// person.Name 는 lvalue

  const char* name = "Gragas";// name는 lvalue
}

- 뭔가 저장 할 수 있는 식을 lvalue라고 한다.
  • rvalue

  • lvalue가 아닌 개체

  • "사용되는 단일 식을 넘어 지속되지 않는 일시적인 값", "컨트롤 할 수 없는 값"

    • 주소가 없는 개체
    • 리터럴(문자열 리터럴 제외)
    • 참조로 반환하지 않는 함수 호출
    • i++와 i-- 는 rvalue
    • 기본적으로 사용되는 산술식, 논리식, 그리고 비교식
    • 열거형
    • 람다
    • 임시적인거...
  • Ex)

// 10은 rvalue, 10이란 것에 값을 쓸 수 있음?? 없다... 임시적이다 이 구문 넘어가면 참조 못함.
int number = 10;
10 = number;// error, 값을 쓸 수 없다.
(number + 1) = 10;// error, 값이 rvalue이기 때문에.

int anotherNumber = 20;
int result = number + anotherNumber;// number + anotherNumber 는 rvalue
&number = 20;// error, &number는 rvalue이기 때문에.

int number1 = 10;
int number2 = 20;
if(number1 < number2)// number1 < number2 도 rvalue
{

}
  • 과거 C++의 문제점

    • 함수 값개체 반환시 2번의 복사
    • rvalue와 복사 생성자.
    vector<float> Math::ConvertToPercentage(const std::vector<float>& scores)
    {
      std::vector<float> percentages;
      for(vector<float>::const_iterator iter = scores.begin(); iter != scores.end(); ++iter)
      {
        percentages 변환
      }
    
      return percentages;// 1번째 복사, percentages를 임시개체로 복사함.
    }
    
    int main()
    {
      vector<float> scores;
      scores = ConvertToPercentage(scores);// 2번째 복사, 임시개체를 scores 로 복사
    }
  • 복사를 어떻게 막을까??

    • rvalue 참조와 이동문법...
  • rvalue 참조, (&&)

    • C++ 11 이후 새로 나온 연산자
    • 기능상 & 연산자와 비슷
    • & 연산자는 lvalue에 참조
    • && 연산자는 rvalue에 참조
    • 임시적인 개체만 참조
  • Ex)

float CalculateAverage()
{
  float average;

  return average;
}

int main()
{
  int number = 10;
  in&& rNumber = number;// error, number는 lvalue 이다.
  int&& rNumber1 = 10;// Ok, 10은 rvalue 이다.
  float&& rAverage = CalculateAverage();// ok, CalculateAverage 는 rvalue
}
  • 정말 중요한것, CalculateAverage() 함수는 rvalue이다
  • CalculateAverage함수가 반환하는 임시값을 참조할 수 있다.
    • std::move()
      • rvalue 참조를 반환
      • lvalue를 rvalue로 반환.
        • move에 lvalue를 넣으면 rvalue로 바꿔줌.
      • 의도를 보이는 문법이다.
        • 유니크 포인터끼리 메모리 주고 받을때 복사 생성을 하면 이상하다. 유니크란것은 하나만 가리키는건데...
        • 그래서 move()라는 함수로 그 의도를 나타낸다. 복사가 아닌 이동 되는것이다.
      • 정리하자면 std::move()는 변수의 소유권을 이전하는 것이다.
      • 유니크 포인터가 std::move()를 받으면 이동생성자가 호출된다, 그리고 이동 생성자는 rvalue만 받는다.
        • 왜 rvalue 만 받을까??? 당연히 lavlue 를 통해 다른 유니크 포인터에서 참조 못하도록 맊기 위해서인것 같다.

이동생성자

  • 복사생성자와 달리 메모리를 복사하는게 아닌 메모리의 소유권을 넘긴다.
  • Ex)
class MyString
{
public:
  MyString(MyString&& other);// 복사 생성자와 굉장히 비슷, const를 뺏다 왜 ?? 원본을 털어올 거니까...
  MyString(const MyString& other);
  MyString(const char* str);
private:
  char* mString;
  int mSize;
}

MyString::MyString(MyString&& other)
  : mString(other.mString)// 얕은 복사를 한다.
  , mSize(other.mSize)
{
  other.mString = nullptr;// 원본을 다 턴다.
  other.mSize = 0;
}

MyString studentName("Lulu");
MyString copiedName(std::Move(studentName));
// studnetName 에는 아무것도 안 남아 있을 것이다 추측할 수 있다.
  • copiedName이 studentName의 값을 뺏어 왔다.
  • 메모리 재 할당을 막을 수 있다.
    • 다른 개체 멤버 변수들의 소유권을 가져옴
    • 복사 생성자와 달리 메모리 재 할당을 하지 않음.
    • 복사 생성자 보다 빠름
    • 약간 얕은 복사와 비슷
      얕은 복사와 다른 점은 이동생성자는 원본을 털어 버린다는거...
    • const를 뺀 이유는 원본을 털어올 거니까...

이동대입연산자

  • Ex)
class MyString
{
public:
  MyString& operator=(MyString&& other);// 이동복사생성자와 비슷
private:
  char* mString;
  int mSize;
}
MyString MyString::operator=(MyString&& other)
  : mString(other.mString) // 얕은 복사를 한다.
  , mSize(other.mSize)
{
  if(this != &other) // 본인이 본인한테 대입하지 않는 경우에만 동작
  {
    // 원래 가지고 있던 메모리 정리.
    // 이동 생성자는 이거 할 필요가 없음, 생성자일 때는 처음 생성하는 거니까...
    // 대입은 기존의 메모리를 가지고 있을 수 있음
    delete[] mString;

    // 얕은 복사.
    mString = other.mString;
    mSize = other.mSize;

    // 원본 털기
    other.mString = nullptr;
    other.mSize = 0;
  }
}


MyString studentName("Lulu");
MyString copiedName("Teemo");// studnetName 에는 아무것도 안 남아 있을 것이다 추측할 수 있다.
studentName = std::move(copiedName);
  • 특수한 상항
    • 본인이 본인한테 대입할 때는??
      • 그때는 아무짓도 안 해도 됨. 함수를 그냥 종료 해야함.
  • 이동 생성자와 정말 같은 개념
  • 다른 개체 멤버 변수들의 소유권을 가져옴
  • 이것도 메모리 재할당 하지 않음.
  • 얕은 복사

STL 컨테이너용 이동 문법

  • C++11이후, STL 컨테이너 이동 생성자와 이동 대입 연산자가 생김
  • 그래서 그것들을 따로 구현할 필요가 없음.
int main()
{
  std::vector<float> scores;

  scores = Math::ConvertToPercentage(scores);

  // scores가 복사가 아닌 임시 개체를 참조 하게 된다.
}
  • 한가지 재미있는 점은 임시개체와 scores가 단순히 참조만 하는것이 아니라 서로 swap을 한다는 점이다.
임시 개체 -> [10][20][30]...(scores가 가지고 있던 메모리)
scores -> [1%][2%][3%] ...(임시개체가 가지고 있던 메모리)

std::swap()이라는 함수가 있음... 이를 이용하는가 보다.

rvalue 최적화

  • 우리를 흥분 시킨 또 다른 C++의 유행어
  • 맞음, 이번에도 우리는 이걸 잘못 사용했음... 남발...
  • 이동 생성자와 이동 대입 연산자.
    • 아직 유효
  • 포인터 대신 개체 자체를 반환하는 함수, 특히 문제!!!
    • 함수에서 rvalue를 반환하는 것은 실제 매우 느림
    • 반환값 최적화(Return value optimize)라고 하는 컴파일러 최적화를 깨트림
      • 함수가 내부에 개체를 만들고 어떤 컨트롤을 하고 반환하면 이것을 무조건 임시 개체로 반환 하는것으로 알고 처음부터
        함수 밖에 개체를 만들어주고 거기에 넣어버림, 최적화를 해줌
        • "최적화를 안 해주는 경우도 있으니 msdn에서 찾아볼 것",
        • "Named rvalue optimization"

베스트 프랙티스

  • 기본적으로 그냥 개체를 반환
  • 더 빨라진다고 입증된 경우에만 함수가 rvalue를 반환하도록 바꾸자.
  • 반환값 최적화가 안되는 경우.
profile
mohadang

0개의 댓글