본문 바로가기
CODING/C++ STUDY

C++ 독학 7일차 - 디폴트 복사 생성자, 생성자 초기화 리스트, const, static, this 포인터

by pharmerci 2021. 7. 28.
728x90

디폴트 복사 생성자

c++ 컴파일러는 디폴트 복사 생성자를 지원해주고 있다.

그냥 1대1 복사를 원하는 것이라면 굳이 복사생성자를 써주지 않고도 디폴트 복사 생성자를 이용해서 쉽게 처리할 수 있다.

 

하지만 디폴트 복사 생성자에 한계가 존재한다. 이 코드는 스타크래프트 코드를 그대로 참고한 것이다.

클래스 Photon_Cannon에 char *name 변수를 만들어줬다.

 

그리고 디폴트 복사 생성자를 이용해서 pc2에 그대로 복사했다.

 

 

 

 

그러면 이렇게 오류가 발생하게 된다.

 

 

 

 

 

디폴트 생성자가 사용될 때 컴파일러는 1대 1로 복사를 해주는 복사생성자를 만들었을 것이다.

이 복사 생성자를 호출하면 hp, shield, .... 모든 변수들이 같아진다. name 변수에서는 두 포인터가 같은 값을 갖는다는 것은 같은 주소값을 가리킨다는 말이다.

pc1의 name이 동적으로 할당받아서 갖고있던 메모리를 pc2의 name도 가리키게 된다는 것이다.

 

main함수가 종료되기 직전 객체들은 파괴되면서 소멸자를 호출하게 된다.

pc1이 먼저 파괴되었다고 했을 때 pc1의 내용을 파괴함과 동시에 할당한 메모리까지 사라지게 된다.

여기서 문제가 발생하는데

pc2의 name은 해제된 메모리를 가리키고 있다는 것이다.

 

 

 

 

 

pc2의 name을 삭제할 때 위의 생성자가 실행될 것이다. 이 함수는 위에 코드에도 나와있다.

 

name은 아까 메모리를 갖고있다고 했으니 NULL은 아니다. 그러면 delete [] name;이 수행될 것이다.

이미 해제된 메모리에 접근해서 다시 해제하라고 했기 때문에 런타임 오류가 발생하게 된다.

 

이를 해결하기 위해서는 복사생성자에서 name을 그대로 복사하지 말고 다른 메모리에 동적 할당을 해서 내용만 복사하면 된다.

이렇게 메모리를 새로 할당해서 내용을 복사하는 것을 deep copy라고 부르고

아까처럼 단순하게 대입만 하는 것은 shallow copy라고 부른다.

 

 

 

 

 

이런식으로 name에 새로운 공간을 할당해서 복사를 해주면

 

 

 

다음과 같이 오류 없이 복사생성자가 호출된다.

 

 

 

 


생성자의 초기화 리스트

이전에는 생성자를 초기화할 때 어떻게 했냐면

예를 들어서

class Student {

int kor;

int eng;

 

public:

Student();

};

 

이런식이라고 하면

 

Student::Student() {

kor=40;

eng=100;

}

 

이렇게 초기화를 해줬는데,

 

Student::Student() : kor(40), eng(100) {}

 

이렇게 할 수 있다. 생성자 호출과 동시에 멤버 변수들을 초기화하게 된다.

 

초기화 리스트를 사용하는 것은 생성과 초기화를 동시에 한다. 초기화리스트를 사용하지 않으면 생성을 먼저하고 이후에 대입을 수행하는 것이다.

 

그러니까 위에 보라색은

int a;

a=10;

이런 느낌이고

 

핑크색은

int a=10;

이런 느낌?

 

아래쪽이 훨씬 간편하다. 효율적이고.

 

레퍼런스와 상수는 생성과 동시에 초기화가 되어야 한다.

클래스 내부에 레퍼런스 변수나 상수를 넣고 싶다면 생성자에서 무조건 초기화리스트를 사용해서 초기화해야 한다.

중요한 값들을 상수로 처리해주면 실수로 상수값을 변경하는 명령 코드를 작성하더라도 컴파일 단계에서 오류가 발생하기 때문에 따로 디버깅을 해줄 필요가 없다.

그래서 훨씬 효율적으로 오류를 발견할 수 있다.

 

 

 


static 변수

지금부터 총 Student의 수를 알아내기 위해 코드를 짠다고 가정하자

간단한 방식으로는

1. 배열에 stdent를 보관해놓고 생성된 student개수를 모두 센다

2. 어떤 변수를 만들어서 전입시 1추가, 전출시 1빼기

1은 크기가 자유자재로 변할 수 있는 배열을 만들어야 한다는 문제점이 있고, 2는 변수를 어떤 함수에서 정의하면 계속 인자로 전달해야 한다는 단점이 있다.

2 방법 중 전역변수를 쓰면 되지 않나! 할 수 있지만 프로젝트 크기가 커지면 오류가 날 수 있어서 필요한 경우가 아니면 안쓴다. 이 문제를 간단하게 해결할 수 있는 기능이 바로 static 멤버 변수이다.

이 변수는 전역변수 같지만 클래스 하나에만 종속되는 변수이다. 프로그램이 종료될 때 변수가 소멸된다. 클래스의 모든 객체들이 공유하는 변수이다. 

 

변수를 선언할 때는 static int student_total; 이런식으로 정의하면 된다.

모든 static변수는 정의와 동시에 0으로 초기화된다.

 

그런데 초기화를 하고싶다면

int Student::student_total=0;

이것도 가능하긴 하다.

클래스 내부에서

class Student {

static int student_total=0;

}

 

이거는 안된다.

이렇게 초기화가 가능한경우는 const static 변수일 때만이다.

 

 

 

클래스 안에는 static 함수도 정의할 수 있다. static 변수가 어떤 객체에 종속되는 것이 아니고 클래스 자체에 1개 존재하는 것처럼 static 함수도 특정 객체에 종속되는 것이 아니라 클래스 전체에 1개 존재하는 함수이다.

 

static이 아닌 멤버 함수들은 객체가 만들어져야지만 멤버 함수들을 호출할 수 있지만

static 함수의 경우 객체 없이도 클래스 자체에서 호출할 수 있다.

 

 

이게 무슨소리냐하면..

예를 들어서

클래스 내부에서

void point_change(int kor, int n);

static void show_total_student();

 

이런 함수 두개를 만들어서 잘 함수 코드를 짰다고 하면

 

main함수에서 이걸 쓸 때

 

point_change 함수는

 

먼저 객체 하나를 생성하고(여기서 student1이라고 하자)

student1.point_change(90, 30);

이런식으로 .을 이용해서 함수를 호출한다.

 

show_total_student 함수는

객체에 종속되는게 아니라 클래스에 종속되는 것이므로

Student::show_total_student();

이렇게 함수를 호출하게 된다. static 함수 내에서는 클래스의 static 변수만을 이용한다. 만약 그냥 클래스의 멤버 변수를 이용하게 되면(여기서 kor) kor가 누구의 kor인지 모르는 상황이 발생한다.

 

 

 

 


THIS

 

먼저 긴 코드 중에 this가 사용되는 코드만 따왔다.

먼저 Marine&을 리턴한다는 것은 맨 위의 주석과 같은 설명이다.

추후 더 자세하게 레퍼런스를 리턴하는 함수에 대해서 공부할 것이다.

 

그러면 마지막에 return *this;는 무엇인가.

역시 그 밑에 잘 설명되어 있다.

 

밑에 주석으로 나타낸 코드는 위에 있는 be_attacked 함수와 완벽하게 같은 뜻이다.

기억할 것은 static 함수에는 당연하게도 this 키워드가 정의되지 않는다는 것!

 

 

 

이 예제 코드를 보면 이해할 수 있을 것이다.

위 코드의 실행 결과는 아래와 같다.

 

 

 

 

 marine2.be_attacked(marine1.attack()).be_attacked(marine1.attack());

 

이것이 this 설명하면서 맨 위에 썼던 코드를 메인함수에서 쓰는 모습인데

이를 실행하면 marine2는 두번 공격이 아니라 한번 공격으로 감소한 HP만 보여진다.

두번째 be_attacked는 marine2가 아닌 엉뚱한 임시 객체에 대해서 호출하는 것이므로 marine2는 marine1의 공격을 1번만 받는다.

728x90