728x90
C++에서 모든 표현식은 Lvalue 또는 Rvalue 이다.

C++11 표준에서는 Lvalue 참조자 외에 Rvalue를 참조할 수 있는 Rvalue 참조자를 추가했다.

Rvalue 참조자는 Visual Studio 2010 이상에서 사용 가능하다.


https://docs.microsoft.com/ko-kr/cpp/cpp/move-constructors-and-move-assignment-operators-cpp?view=vs-2019 에 이동 생성자(Move constructor) 및 이동 대입 연산자(Move assignment operator)에 대한 사용법이 나와있다.


int a = 3;
a 는 메모리 상에서 존재하는 변수다. a 의 주소값을 & 연산자를 통해 알아 낼 수 있다.
보통 이렇게 주소값을 취할 수 있는 값을 Lvalue 라고 부른다.
그리고 Lvalue는 어떠한 표현식의 왼쪽 오른쪽 모두에 올 수 있다(왼쪽에만 와야 하는게 아니다).

반면에 오른쪽에 있는 '3' 을 살펴보자. '3' 은 주소값을 취할 수 있는게 아니다.
'3' 은 위 표현식을 연산할 때만 잠깐 존재하고 연산후에는 사라지는 값이다. 즉, '3' 은 실체가 없는 값입니다.
이렇게, 주소값을 취할 수 없는 값을 Rvalue 라고 부른다.
Lvalue가 식의 왼쪽 오른쪽 모두 올 수 있는 반면, Rvalue는 식의 오른쪽에만 존재해야 한다.

C++ 에서 int& b = a; 형태로 사용하는 reference(참조자, 참조변수)는 Lvalue 참조자이다.


C++ 클래스에는 기본 생성자, 복사 생성자, 이동 생성자가 있다.

포인터를 멤버로 가지는 클래스는 복사 생성자에서 깊은 복사(deep copy)를 수행한다.

C++ 11에는 Rvalue reference를 파라미터로 갖는 새로운 타입의 생성자가 추가되었으며, 이를 이동 생성자(move constructor)라고 한다. 이동 생성자는 호출되면 얕은 복사(shallow copy)를 하고 원본의 소유권을 대상으로 이전(move)하는 방식으로 객체를 생성한다. 원본 객체를 NULL로 초기화하여 접근할 수 없게 만든다.


Person::Person(Person&& other) noexcept {
    name = other.name; // 얕은 복사
    phone = other.phone; // 얕은 복사
    age = other.age;

    // 임시 객체 소멸 시에 메모리를 해제하지 못하게 한다.
    other.name = nullptr; // nullptr 는 C++ 11 에 추가된 키워드로, 기존의 NULL 대체
    other.phone = nullptr; // 이전 객체에서 pointer는 삭제
    other.age = 0;
}


이동생성자의 호출 조건
1. 임시객체를 전달할 때.
2. std::move()를 사용하여 인자를 rValue참조자라 불리는걸로 변환하여 전달할때.


예제 1과 같이 이동 생성자와 복사 생성자를 모두 구현해 놓으면, 코드 상황에 따라 복사가 되거나, 이동이 된다.

예제1.

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

class Person {
    char* name;
    char* phone;
    int age;
public:
    Person();
    Person(const char _name[], const char _phone[], int _age);
    ~Person();
    Person(const Person& p); // 복사 생성자 선언
    Person& operator=(const Person& p); // 복사 이동 연산자 선언
    Person(Person&& p) noexcept; // 이동 생성자 선언
    Person& operator=(Person&& p) noexcept; // 이동 복사 연산자 선언
    void ShowData(); // 선언

    //Setter method
    void setName(const char _name[]) {
        name = new char[strlen(_name) + 1]; // NULL문자를 고려하여 +1만큼 할당
        strcpy_s(name, strlen(_name) + 1, _name);
    }
    void setPhone(const char _phone[]) {
        phone = new char[strlen(_phone) + 1];
        strcpy_s(phone, strlen(_phone) + 1, _phone);
    }
    void setAge(int age) { this->age = age; }

    // Getter method
    string getName() { return name; }
    string getPhone() { return phone; }
    int getAge() { return age; }
};

Person::Person() {
    cout << " 매개변수 없는 생성자 호출- 메모리 주소 : " << this << endl;
    name = NULL;
    phone = NULL;
    age = 0;
}

Person::Person(const char _name[], const char _phone[], int _age) {
    cout << " 매개변수 3개 생성자 호출- 메모리 주소 : " << this << endl;
    name = new char[strlen(_name) + 1]; // NULL문자를 고려하여 +1만큼 할당
    strcpy_s(name, strlen(_name) + 1, _name);

    phone = new char[strlen(_phone) + 1];
    strcpy_s(phone, strlen(_phone) + 1, _phone);

    age = _age;
}

Person::~Person() { // DeAllocate the heap
    cout << " 소멸자 호출- 메모리 주소 : " << this << endl;
    delete[]name;
    delete[]phone;
}

Person::Person(const Person& p) : age(p.age) {
    cout << " Copy constructor 호출- 메모리 주소 : " << this << endl;
    name = new char[strlen(p.name) + 1]; // NULL문자를 고려하여 +1만큼 할당
    strcpy_s(name, strlen(p.name) + 1, p.name); // 깊은 복사
    // The statement name = new char; will create the new heap location
    // and then copies the value of obj content to new heap location.

    phone = new char[strlen(p.phone) + 1];
    strcpy_s(phone, strlen(p.phone) + 1, p.phone);
}

Person& Person::operator=(const Person& p) {
    cout << " Copy assignment operator 호출- 메모리 주소 : " << this << endl;
    if (this != &p) {
        delete[]name;
        delete[]phone;

        name = new char[strlen(p.name) + 1]; // NULL문자를 고려하여 +1만큼 할당
        strcpy_s(name, strlen(p.name) + 1, p.name); // 깊은 복사

        phone = new char[strlen(p.phone) + 1];
        strcpy_s(phone, strlen(p.phone) + 1, p.phone);

        age = p.age;
    }
    return *this;
}

Person::Person(Person&& other) noexcept {
    cout << " Move constructor 호출 - 메모리 주소 : " << this << endl;
    name = other.name; // 얕은 복사
    phone = other.phone; // 얕은 복사
    age = other.age;

    // 임시 객체 소멸 시에 메모리를 해제하지 못하게 한다.
    other.name = nullptr; // nullptr 는 C++ 11 에 추가된 키워드로, 기존의 NULL 대체
    other.phone = nullptr; // 이전 객체에서 pointer는 삭제
    other.age = 0;
}

Person& Person::operator=(Person&& other) noexcept {
    cout << " Move assignment operator 호출 - 메모리 주소 : " << this << endl;
    if (this != &other) {
        delete[]name;
        delete[]phone;

        name = other.name; // 얕은 복사
        phone = other.phone; // 얕은 복사
        age = other.age;

        other.name = nullptr; // 이전 객체에서 pointer는 삭제
        other.phone = nullptr;
        other.age = 0;
    }
    return *this;
}

void Person::ShowData() { // 클래스 외부에서 클래스 멤버 함수 정의
    cout << " name : " << name << ", phone : " << phone << ", age : " << age << endl;
}


Person setData() {
    Person tmp;
    tmp.setName("이순신");
    tmp.setPhone("010-9999-8888");
    tmp.setAge(54);
    return tmp;
}


int main() {
    cout << endl; // 출력 구분 목적
    //Person p1("홍길동", "010-1234-5555", 34);
    Person p1;
    p1 = setData(); // 얕은 복사
    p1.ShowData();

    p1.setAge(40);

    cout << endl;
    Person p2(p1); // 복사 생성자 호출
    //Person p2 = std::move(p1); //move 함수를 이용해서 p1를 생성할 때 이동생성자를 호출하게 함
    p2.ShowData();

    cout << endl;
    Person p3;
    p3 = p2; // p3.operator=(p2); // 복사 대입 연산자 호출
    //p3 = std::move(p2); // 이동 대입 연산자 호출
    p3.setAge(35);
    p3.ShowData();

    // 객체가 소멸되는 순서는 객체가 생성된 순서의 반대다.
    cout << endl;
}
 


완벽한 예제가 못되는 거 같아 좀 아쉽다. 좀 더 가다듬게 되면 수정해 놓을 것이다.

728x90

'C++ > C++ 문법' 카테고리의 다른 글

C++ 포인터 이해  (0) 2019.11.30
C++ 클래스 상속  (0) 2019.11.29
C++ 복사 대입 연산자(copy assignment operator)  (0) 2019.11.28
C++ 얕은 복사, 깊은 복사(Shallow Copy vs. Deep Copy)  (0) 2019.11.27
C++ 복사 생성자  (0) 2019.11.27
블로그 이미지

Link2Me

,