C++.6 깊은복사

lsw·2021년 4월 4일
0

C++

목록 보기
6/8
post-thumbnail

1. 내용

얕은복사시 발생하는 메모리공간 낭비, 소멸자오류(가리키는 대상 소멸로 인한 오작동)를 방지하기위해 객체복사 방법인 "복사생성자" 호출방식과 "대입연산자" 호출방식을 조작해 깊은복사를 진행해보자.


2. 코드 & 상세리뷰

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

class Book
{
// 이처럼 동적할당이 필요한 경우에 깊은복사가 요구된다.
    char* title;
    char* isbn;
    int price;

public:
// 생성자, default값 설정.
    Book(const char* tit=" ", const char* is=" ", int price=0)
        :price(price)
    {
// 크기 미정의 char형 포인터를 적합한 크기로 동적할당.
        int len1 = strlen(tit) + 1;
        int len2 = strlen(is) + 1;
        title = new char[len1];
        isbn = new char[len2];
        strcpy(title,  tit);
        strcpy(isbn, is);
    }

/* 
<복사생성자>
복사생성자를 call할 복사주체는 선언과 동시에 초기화(복사)되기에 잔존메모리가 없어 별도로
 비울 필요가 없다.
*/
    Book(Book& Bcopy)
        :price(Bcopy.price) // 복사생성자 또한 이니샬라이저를 사용할 수 있다.
    {
        int len1 = strlen(Bcopy.title) + 1;
        int len2 = strlen(Bcopy.isbn) + 1;
        title = new char[len1];
        isbn = new char[len2];
        strcpy(title, Bcopy.title);
        strcpy(isbn, Bcopy.isbn);
    }
// 깊은 복사를 진행하여 오류 방지

/*
 <대입연산자>
 이를 call할 복사 주체중 배열포인터는 이미 다른값(or default)으로 초기화 돼 있는
 상태기에(최소 디폴트 값이라도) 누수를 방지하기 위해 메모리를 비워야 한다.
*/
    Book& operator=(Book & Bcopy) // 대입연산자는 이니셜라이저를 사용하지 못한다
    {
// 기존 동적할당 메모리를 비우는 과정
        delete []title;
        delete []isbn;

        int len1 = strlen(Bcopy.title) + 1;
        int len2 = strlen(Bcopy.isbn) + 1;
        title = new char[len1];
        isbn = new char[len2];
        strcpy(title,  Bcopy.title);
        strcpy(isbn, Bcopy.isbn);
        price = Bcopy.price;
        return *this;
    }

//소멸자
    ~Book()
    {
        delete []title;
        delete []isbn;
        cout << "Book Destructor called" << endl;
    }

    void Show()
    {
        cout << "책명 : " << title << endl;
        cout << "코드 : " << isbn << endl;
    }
};

// 전자도서 is 도서, 상속 진행
class Ebook : public Book
{
    char* eisbn;
public:
// 생성자, default 설정
    Ebook(const char* title=" ", const char* isbn=" ", int price=0, const char* eis=" ")
        :Book(title, isbn, price)
    {
        int len = strlen(eis) + 1;
        eisbn = new char[len];
        strcpy(eisbn, eis);
    }

// 복사 생성자. **기초클래스의 복사생성자를 차용할 수 있다, 메모리를 비울 필요가 없다.**
    Ebook(Ebook& Ecopy)
        : Book(Ecopy)
    {
        int len = strlen(Ecopy.eisbn) + 1;
        eisbn = new char[len];
        strcpy(eisbn, Ecopy.eisbn);
    }

/*
 <대입 연산자>
 생성자, 복사생성자가 기초 - 유도 클래스의 상속관계에 있을 시 유도클래스에서
  1) 기초클래스의 디폴트 생성, 복사생성자는 별도로 명시하지 않고 차용
  2) 기초클래스에서 생성, 복사생성자를 디폴트 이외 별도 정의 시 이니셜라이저를 통해 차용
 할 수 있는것과 달리 대입연산자는 class::operator=(class & classcopy)로 직접 대입연산자
 함수를 불러들여야 한다.
*/
    Ebook& operator=(Ebook& Ecopy)
    {
        Book::operator=(Ecopy); 
/* Book이 Ebook에 의해 상속되는 상위 클래스이기에 매개변수 Book & ~ 는 
   Ebook형 객체를 가리킬 수 있다.
*/
        delete []eisbn;
        int len = strlen(Ecopy.eisbn);
        eisbn = new char[len];
        strcpy(eisbn, Ecopy.eisbn);
        return *this;
    }

    ~Ebook()
    {
        delete []eisbn;
        cout << "Ebook Destructor called!" << endl;
    }

    void Show()
    {
        Book::Show();
        cout << "e코드 : " <<  eisbn << endl;
    }

};

int main()
{
    Ebook ebook1("경영학원론", "1234-5678", 40000, "123-567");
    Ebook ebook2; // default값으로 생성(변수 초기화)
    ebook2 = ebook1; // 대입연산자 호출
    ebook2.Show();
    Ebook ebook3=ebook1; // 선언 동시 복사 초기화(복사생성자 호출)
    ebook3.Show();
    return 0;
}

3. 결과

  • 복사 생성자, 대입연산자를 이용한 복사방법 모두 잘 작동되며, 소멸자역시 이상없이 호출되고 있다.

4. 결론

복사생성자는 객체를 선언과 동시에 복사 초기화 시 호출하는 함수, 대입연산자는 기존 초기화된 객체를 다른 객체로 복사 시 호출된다는 점을 알았다. 또한 멤버변수로 배열이 있을경우 복사과정은 문제가 없으나 같은 주소를 공유해 가리키는 문제로, 객체 하나가 소멸되며 배열 변수가 참조하는 값이 소멸되면 나머지 한 객체의 해당 변수는 가리킬 곳을 잃어 소멸자 호출시 메모리 누수가 발생함을 알았다.

profile
미생 개발자

0개의 댓글