티스토리 뷰

대입 연산자의 오버로딩 

- 대입연산자의 오버로딩은 복사 생성자와 매우 유사하다.

 

- 둘 의 호출 시점에 차이가 있다.

 

복사 생성자

int main() 
{
    Point pos1(1, 2);
    Point pos2 = pos1; // 복사 생성자 호출 
}

복사 생성자는 위와 같이 새로 생성하는 pos2 객체의 초기화에 기존에 존재하던 pos1 객체를 사용한다.

 

대입 연산자 오버로딩

int main()
{
    Point pos1(1, 2);
    Point pos2(3, 4);
    pos2 = pos1; // pos2.operator=(pos1) 
}

대입 연산자의 오버로딩은 위와 같이 이미 존재하는 두 객체같의 대입연산시 호출된다. 

 


디폴트 대입 연산자 

대입 연산자도 복사 생성자와 같이 정의 되어 있지 않으면 디폴트 대입 연산자가 자동 삽입된다. 

그런데 디폴트 대입 연산자도 디폴트 복사 생성자와 거의 같은 문제를 발생시킨다.

디폴트 복사 생성자와 마찬가지로 디폴트 대입 연산자도 얕은 복사를 진행하기 때문에 생성자 내에서 동적 할당을 하는 경우 객체가 소멸될때 두 가지 문제가 발생한다.

 

1. 데이터를 가르키는 곳의 주소 값을 잃게 된다.  (메모리 누수) 

=> 하나의 객체에 또 다른 객체로 얕은 복사를 진행하면 기존에 가르키던 데이터의 주소 정보가 덧씌워 지므로 데이터의 주소 값을 잃게 된다.

 

2. 객체의 소멸 과정에서 중복 소멸이 일어난다. 

=> 얕은 복사 진행 시, 두 객체는 같은 주소값 정보를 갖고있다. 

이때 두 객체의 소멸이 진행되면 먼저 하나의 객체가 소멸될때 이 주소값이 제거된다.

그리고 다음 객체의 소멸이 진행될때 같은 주소값의 소멸이 진행된다.

 

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

class Person
{
private:
    char *name;
    int age;
public:
    Person(char *myname, int myage) : age(myage)
    {
        name = new char[strlen(myname)+1];
        strcpy(name, myname);
    }
    void ShowPersonInfo()
    {
        cout << "이름: " << name << endl;
        cout << "나이: " << age << endl;
    }
    Person& operator= (const Person& ref)
    {
        // 이 객체의 name의 주소값은 새로 할당될 것이기 때문에 필요가 없다
        // 메모리 누수를 위해 이미 존재하는 주소값은 삭제한다 
        delete[] name; 
        age = ref.age;
        name = new char[strlen(ref.name)+1];
        strcpy(name, ref.name);
        return *this;
    }
    ~Person()
    {
        delete[] name;
        cout << "called Destructor" << endl;
    }
};

 


상속 구조에서의 대입 연산자 호출 

- 생성자: 유도 클래스의 생성자 아무런 명시를 하지 않아도 기초 클래스의 생성자가 호출된다.

- 대입 연산자: 유도 클래스의 대입 연산자에 아무런 명시를 하지 않으면 기초 클래스의 대입 연산자는 호출 되지 않는다.

 

따라서 다음과 같이 기초 클래스인 First 클래스의 대입 연산자를 명시적으로 호출해줘야 한다.

// 기초 클래스: First, 유도 클래스: Second 
Second& operator= (const Second& ref)
{
    First::operator=(ref); 
    num3 = ref.num3;
    num4 = ref.num4;
    return *this;
}

 


문제 11-1 [깊은 복사를 하는 대입 연산자의 정의]

문제 1 

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

class Gun
{
private:
    int bullet;
public:
    Gun(int bnum) : bullet(bnum) {}
    void Shot()
    {
        cout << "BBANG!" << endl;
        bullet--;
    }
};

class Police
{
private:
    int handcuffs;
    Gun *pistol;
public:
    // 생성자
    Police(int bnum, int bcuff) : handcuffs(bcuff)
    {
        if(bnum > 0)
            pistol = new Gun(bnum);
        else
            pistol = NULL;
    }
    // 복사 생성자
    Police(const Police &ref) : handcuffs(ref.handcuffs)
    {
        if(ref.pistol != NULL)
            pistol = new Gun(*(ref.pistol)); // Gun의 디폴트 복사 생성자 호출
        else
            pistol = NULL;
    }
    // 대입 연산자 오버로딩
    Police& operator= (const Police &ref)
    {
        // 현재 객체에 저장된 pistol 주소 정보 있다면 메모리 누수 방지 위해 삭제
        if(pistol != NULL)
            delete pistol;

        // 전달 받은 객체에 pistol 주소 정보 있다면 깊은 복사 진행
        if(ref.pistol != NULL)
            pistol = new Gun(*(ref.pistol));
        else
            pistol = NULL;

        handcuffs = ref.handcuffs;
        return *this;
    }

    void PutHandcuff()
    {
        cout << "SNAP!" << endl;
        handcuffs--;
    }
    void Shot()
    {
        if(pistol == NULL)
            cout << "No Pistol" << endl;
        else
            pistol->Shot();
    }
    ~Police()
    {
        if(pistol != NULL) delete pistol;
    }
};

int main()
{
    Police police1(2, 3);
    Police police2 = police1; // 복사 생성자 호출
    police2.Shot();

    Police police3(1,1);
    police3 = police2; // 오버로딩된 대입 연산자 호출
    police3.PutHandcuff();
}

 

 

문제 2

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

class Book
{
private:
    char * title;
    char * isbn; // 국제표준도서번호
    int price;

public:
    Book(char * _title, char * _isbn, int _price) : price(_price)
    {
        title = new char[strlen(title) + 1];
        strcpy(title, _title);
        isbn = new char[strlen(_isbn) + 1];
        strcpy(isbn, _isbn);
    }
    // 복사 생성자
    Book(const Book &ref) : price(ref.price)
    {
        title = new char[strlen(ref.title)+1];
        strcpy(title, ref.title);
        isbn = new char[strlen(ref.isbn)+1];
        strcpy(isbn, ref.isbn);
    }
    // 대입 연산자 오버로딩
    Book& operator= (const Book &ref)
    {
        // 기존 주소값 삭제 (메모리 누수 방지) 
        delete []title;
        delete []isbn;

        price = ref.price;
        // 동적 할당 
        title = new char[strlen(ref.title)+1];
        strcpy(title, ref.title);
        isbn = new char[strlen(ref.isbn)+1];
        strcpy(isbn, ref.isbn);
        return *this;
    }

    void ShowBookInfo()
    {
        cout << "제목: " << title << endl;
        cout << "ISBN: " << isbn << endl;
        cout << "가격: " << price << endl;
    }

    ~Book()
    {
        delete []title;
        delete []isbn;
    }

};

class EBook : public Book
{
private:
    char * DRMKey;

public:
    EBook(char * _title, char * _isbn, int _price, char * _DRMKey)
            : Book(_title, _isbn, _price)
    {
        DRMKey = new char[strlen(_DRMKey) + 1];
        strcpy(DRMKey, _DRMKey);
    }
    // 복사 생성자
    EBook(const EBook &ref) : Book(ref)
    {
        DRMKey = new char[strlen(ref.DRMKey)+1];
        strcpy(DRMKey, ref.DRMKey);
    }
    // 대입 연산자 오버로딩
    EBook& operator= (const EBook &ref)
    {
        Book::operator=(ref);
        delete []DRMKey; // 기존 정보 삭제
        DRMKey = new char[strlen(ref.DRMKey)+1];
        strcpy(DRMKey, ref.DRMKey);
        return *this;
    }

    void ShowEBookInfo()
    {
        ShowBookInfo();
        cout << "인증키: " << DRMKey << endl;
    }

    ~EBook()
    {
        delete []DRMKey;
    }
};

int main()
{
    EBook ebook1("좋은 C++ ebook", "555-12345-890-1", 10000, "fdx9w018kiw");
    EBook ebook2 = ebook1; // 복사 생성자 호출
    ebook2.ShowEBookInfo();
    cout << endl;

    EBook ebook3("dumy", "dumy", 0, "dumy");
    ebook3 = ebook2; // 대입 연산자 호출
    ebook3.ShowEBookInfo();
}

 

 


이니셜라이저로 초기화 했을시 성능향상 이유 

- 이니셜라이저로 초기화하면 대입 연산자로 초기화 했을때 보다 약간의 성능상 이득이 있다.

다음과 같이 이니셜라이저에 의해 객체를 초기화 하면 

class BBB
{
private:
    AAA mem;
public:
    BBB(const AAA &ref) : mem(ref) {} // 이니셜라이저에 의한 초기화 
};

"이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성"되므로 

mem(ref)AAA mem = ref 로 컴파일된다.

 

반면 다음과 같이 대입연산에 의해 초기화 하면 

class CCC
{
private:
    AAA mem;
public:
    CCC(const AAA &ref) { mem = ref; } // 대입연산에 의한 초기화 
};

"생성자의 몸체 부분에서 대입연산을 통한 초기화를 진행하면, 선언과 초기화를 각각 별도의 문장에서 진행하는 형태로 바이너리 코드가 생성"되므로 생성자와 대입연산자가 각각 한번씩 호출된다. 

 

따라서 함수 호출의 횟수가 1번 적은 이니셜라이저에 의한 초기화가 약간이나마 성능에서 이득이 있다.

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함