프로그래밍/C++

[C++] 객체의 복사 방법

갓똥 2019. 11. 13. 23:29
728x90
반응형

1. 객체의 복사 방법

#include <iostream>
#include <cstring>

class Person {
    char* name;
    int   age;
public:
    Person(const char* n, int a) : age(a) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }
    ~Person() { delete name; }
};

int main() {
    Person p1("kim", 20);
    Person p2 = p1;
}

  얕은 복사란? (Shallow Copy)

    => 클래스 안에 포인터 멤버가 있을 때 디폴트 복사 생성자가

    => 메모리 자체를 복사하지 않고 주소만 복사 하는 현상

 

#include <iostream>
#include <cstring>

class Person {
    char* name;
    int   age;
public:
    Person(const char* n, int a) : age(a) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }
    ~Person() { delete name; }
    
    Person(const Person& p) : age(p.age) {
        // 포인터는 복사 하지말고, 새롭게 메모리 할당
        name = new char[strlen(p.name) + 1];
        strcpy(name, p.name);
    }
};

int main() {
    Person p1("kim", 20);
    Person p2 = p1;
}

  깊은 복사 (Deep Copy)

    => 클래스 안에 포인터 멤버가 있을 때 

    => 메모리 주소를 복사 하지 말고 메모리 자체의 복사본을 만드는 기술

 


2. 단점 및 해결책

  깊은 복사 (Deep Copy)의 단점

    => 객체를 여러 번 복사하면 동일한 자원(이름)이 메모리에 여러 번 놓이게 된다.

    => 자원의 크기가 큰 경우 메모리 낭비가 발생.

 

  참조 계수 (reference counting)

    => 여러 객체가 하나의 자원을 공유하게 한다.

    => 단, 몇 명의 객체가 자원을 사용하는지 개수를 관리한다.

 


3. 참조 계수 방법

#include <iostream>
#include <cstring>

class Person {
    char* name;
    int   age;
    int*  ref;
public:
    Person(const char* n, int a) : age(a) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
        
        // ref = new int(1); - 아래와 동일 코드
        ref = new int;
        *ref = 1;
    }
    ~Person() { 
        // 참조 계수 기반인 경우의 소멸자
        if( --(*ref) == 0 ) {
            delete[] name;
            delete ref;
        }
    }
    
    Person(const Person& p) : name(p.name), age(p.age), ref(p.ref) {
        ++(*ref);
    }
};

int main() {
    Person p1("kim", 20);
    Person p2 = p1;
}

 

멤버 데이터 / 생성자 / 복사 생성자 / 소멸자가 바뀌었다.

  참조 계수 (reference counting)에서 좀 더 생각해야 할 점

    => p1 객체가 자신의 이름을 변경하면 어떻게 될까?

    => p2의 이름은 변경되면 안되므로 공유 했던 자원은 분리되어야 한다.

        -> 참조 계수도 마찬가지로 분리되어야 한다.

    => 멀티 스레드 환경에서는 동기화의 오버헤드가 추가된다.

 


4. 이외의 복사 기법들

4.1 복사 금지

#include <iostream>
#include <cstring>

class Person {
    char* name;
    int   age;
public:
    Person(const char* n, int a) : age(a) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }
    ~Person() { delete name; }
    
    Person(const Person& p) = delete;
};

int main() {
    Person p1("kim", 20);
    Person p2 = p1;  // 컴파일 에러가 나오게 하자.
}

  ① 복사 금지

    => 객체를 복사하지 못하게 하자는 의도

    => 복사 새성자를 delete 한다.

 

4.2 using STL

#include <iostream>
#include <cstring>
#include <string>

class Person {
    std::string name;
    int   age;
public:
    Person(const std::string n, int a) : name(n), age(a) {
    }
};

int main() {
    Person p1("kim", 20);
    Person p2 = p1;
}

  ① 문자열이 필요하면 STL의 string클래스를 사용하자.

    => 동적 메모리 할당을 할 필요 없다.

    => string이 내부적으로 자원을 관리해준다.

    => int 변수처럼 사용하면 된다.

728x90
반응형