2021. 12. 9. 19:25ㆍc++
https://gt40766.tistory.com/40?category=1061306
이 게시글에서 복사 생성자를 먼저 읽고 오자.
10. 소멸자와 복사 생성자가 궁금해.
소멸자 c++는 우리가 임의로 할당한 메모리에 관해선 직접 메모리를 삭제해주어야 한다. 안 그럼 메모리에 그대로 그 값이 저장되어 컴퓨터의 용량을 차지한다. (다행히 재부팅하면 초기화 됨.)
gt40766.tistory.com
c++에서 클래스라는 개념이 중요하다.
class와 관련된 개념 중 하나가 매우 중요한 copy control이다.
The Copy Constructor

1. 첫 번째 파라메터가 클래스 타입의 레퍼런스이다. (거의 항상 const)
2. 항상 정의하는 것은 아니다.(디폴트로 내장되있는 것을 쓸 때도 있어서)

이건 할당이 되는 것이 아니다. f2객체가 생성되고 f1의 값을 복사 하는 것.
the synthesized copy constructor
생성자를 정의하지 않았을 때 컴파일러가 자동으로 정의 하는 것.
synthesized constructor와 default constructor는 같은 의미.
매개변수로 들어온 객체의 모든 멤버를 복제한다.
이때 멤버가 class type일 경우 내부에서 또 copy constructor가 실행된다.

위를 보고 direct initialization과 copy initialization(복사 생성자를 사용)의 차이를 알아야 한다.
첫 번째 코드에서 .를 10개 가진 문자열 객체가 다이렉트 초기화를 통해 생성된다.
두 번째 코드는 다른 string 객체를 받아들이는 생성자가 있기 때문에 다이렉트 초기화를 통해 생성된다. (?)
세 번째 코드는 앞서 설명하였듯 카피 초기화가 된다.?
네 번째 코드에 rvalue는 string type은 아니지만 convert 되어 적절한 생성자에 자동으로 들어간다.
다섯 번째 코드는 rvalue가 다이렉트 초기화가 된 뒤에 nines의 객체에 값이 복사가 된다.
다이렉트 초기화를 할 때는 컴파일러에게 적절한 매개 변수가 있는 function matching을 사용하는 것을 요청한다.
복사 초기화를 할 때는 컴파일러에게 생성이 된 객체에 우측 값을 복사하는 연산을 요청한다.
복사 초기화(Copy initialization)가 발생하는 상황
1. 대입 연산자 (=)를 사용해서 변수를 정의할 때
2. 함수의 매개변수로 객체를 사용할 때, 레퍼런스 타입이 아닌 경우 (레퍼런스는 값을 직접 접근.)
3. 함수의 return이 객체를 반환할 경우, 레퍼런스 타입이 아닌 경우.
4. aggregate class나 array를 {}로 초기화할 때
매개변수가 레퍼런스 타입이 아닐 경우에 복사 초기화가 발생한다.
그렇기 때문에 복사 생성자를 정의할 때는 반드시 레퍼런스 타입을 매개변수로 받아야 한다.
왜냐하면 복사 생성자가 레퍼런스 타입이 아닐 경우 복사 초기화가 발생하기 때문.
복사 생성자가 허용되지 않을 때

위 첫 줄 코드를 실행하면 10의 size를 가진 vector가 생성된다.
두번 째 코드가 error가 생기는 이유는 생성자가 size를 매개변수로 받는 생성자가 explicit이기 때문 (implicit 형변환 x)
The Copy assignment operator

이미 f1과 f2를 생성이 되어 있고, 대입연산자로 f1을 f2를 할당할 수 있다.
사실 대입연산자 =는 function이다. (operator=, left-hand를 return하는 함수.)

이렇게 쓸 수도 있다. 정확히 똑같은 의미이다.
위 함수는 정의하지 않아도 컴파일러가 정의해준다. (synthesized copy-assignment operator)

자동으로 생성되는 operator= 의 모습
The Destructor

소멸자는 디폴트 생성자 앞에 ~를 붙여져 있는 모습으로 생성할 수 있다.
객체가 사용한 리소스를 돌려주고, 객체의 nonstatic data의 멤버를 destroy하는 처리가 필요할 떄 사용한다.
해당 객체가 scope를 벗어날 때 등 destroy 될 때 호출 됨.
해당 객체의 멤버인 포인터가 dynamic alocated 된 메모리를 가리키고 있을 때, 해당 객체가 destroy가 된다면,
dynamic alocated에 접근할 수 있는 방법이 사라지게 된다. 누수 (momry leak)가 발생.
소멸자에 해당 메모리를 delete 해주면 해결할 수 있음.
그 다음은 nonstatic data member가 destroy가 된다. (이 부분은 implicit함)
만일 data member가 class 타입이면 그 자신의 소멸자가 호출 됨.
소멸자가 호출 될 때
객체가 destroy될 때, 소멸자는 자동으로 사용된다.
1. 변수가 scope를 벗어나서 destroy될 때
2. 객체 멤버가 destroy될 때
3. 컨테이너의 elements들은 컨테이너가 destroy될 때, destroy된다.
4. dynamic alocated된 메모리를 delete 명령을 적용하면 destroy가 된다.
5. 임시 객체(return에 객체를 반환하는 함수를 사용할 경우 등)는 모든 코드가 실행될 때 destroy 된다.
지역 변수들은 scope를 벗어나면 컴파일러에서 소멸자로 모두 destroy됨.
컴파일러는 소멸자를 클래스 또한 정의하지 않더라도 synthesize가 더 된 버젼으로 자동으로 생성함.
일반적으로 소멸자의 body는 비어있음. (모든 멤버는 소멸자가 호출된 뒤 destroy됨. (implicit))
위 복사 생성자, =연산자, 소멸자는 클래스 객체를 복사들을 다루는 기본적인 세 가지 명령이다.
클래스에 고유한 버젼의 위 멤버를 정의해야 할지 말지 결정하는 법.
소멸자가 필요한지 먼저 정해라. 소멸자가 필요하다면 복사 생성자와 =연산자도 정의해야 한다!
하지만 복사 생성자와, =연산자 필요하더라도 소멸자가 안 필요할 수 있음.
만일 분명하게 컴퍼일러에게 synthesize 버젼의 copy control 멤버를 생성하고 싶다면 = default를 해주면 됨

(이렇게 안 해도 자동으로 생성해줄 텐데 왜 이렇게 해주는 걸까)

복사를 허용하지 않고 싶다면 =delete 를 해주도록 한다.
소멸자는 위처럼 deleted member가 될 수 없다. 왜냐하면 소멸자는 항상 존재해야 하기 때문이다.
1. 클래스 타입의 지역 변수가 있을 경우 scope 밖으로 나가면 소멸자를 호출.
2. 힙영역에 저장되는 dynamic allocate일 경우에도 사용자가 직접 소멸자를 호출.
synthesize member가 위 deleted가 되는 경우
클래스의 멤버중에 고유한 소멸자가 deleted거나 접근할 수 없으면(private) synthesized 소멸자는 deleted로 정의 되어 있다.
클래스의 멤버중에 copy constructor가 delete거나 접근할 수 없으면 synthesized copy constructor도 deleted로 정의 된다.
클래스의 멤버 중에 고유한 =연산자가 deleted 혹은 접근할수 없거나 const 타입, 레퍼런스 멤버가 있다면 =연산자는 deleted로 정의 된다. (const 와 레퍼런스는 꼭 선언과 동시에 초기화해야하기 때문에)
만일 위에 해당하는 멤버가 있다면 own version은 만들어 사용하라는 뜻인듯!

만일 pivate에 복사 생성자와 =연산자를 정의한다면 delete처럼 복사를 방지할 수 있다.
원래 클래스 내부에서 dynamic allocate를 했다면 반드시 copy control member를 정의해야 한다.

위 예시처럼 string 포인터 멤버가 있다. 이 멤버는 dynamic allocate를 한 string을 가르킨다.
복사 연산자와 =연산자를 다시 정의해주고 소멸자로 ps를 delete해주어야 한다.

=연산자를 정의한 모습 우항(right-operand)에 있는 값을 좌항(left-operand)에다 복사를 해준다.
우선 이미 할당이 되어 있는 ps를 해제해주고 우항의 값을 복사해 새로 할당해준다 그 후 자기자신을 return (*this) 해준다.
하지만 위 방법은 문제가 있다. 바로 자기 자신을 복사하지 못한다는 점이다. (굳이 왜 그러냐만은..)
ps를 delete를 해주면 스스로 ps 값을 가져오는 것이 불가능하기 때문이다.

이렇게 지역 변수에 저장해놓고 사용하면 그 문제는 해결된다.
또한 아래와 같이 구현할 수 있다. 아래 코드로 구현을 할 때 특이한 점은 객체를 생성자를 통해서 dynamic allocate를 한다. 그리고 복사 생성자로 할당된 객체는 원래 객체와 같은 ps를 공유하고 있다. 또한 use라는 변수에 현재 얼마나 많은 객체들이 해당 ps를 가르키고 있는지 heap에 저장한다. (dynamic allocate)

소멸자로 위 ps와 use를 그냥 해제해주면 안된다. 힙에 저장된 ps와 use를 공유하고 있기 때문이다.

또한 =연산자를 사용하면 오른쪽의 객체가 왼쪽의 객체의 ps, use가 가르키고 있는 메모리를 가르켜야 하기 때문에
아래의 과정이 필요하다. (자기 자신도 올 수 있게 먼저 오른쪽 객체의 use를 증가시켜야 한다.)

Swap

swap을 이런 식으로 하면 쉽게 구현할 수 있겠지만 효과적이지 않다.
복사를 할 때 dynamic allocate가 발생하기 때문이다. (new와 delete가 쓸데 없이 발생함)

직접 pointer의 값을 교환해주면 효율적임.

위의 ps를 바꿔주는 부분은 algorithm 라이브러리의 swap이다. 하지만 ps와 i는 private이니 class에 friend 함수로 선언을 해주어야 한다.

swap을 이용해서 =연산을 해줄 수 있다. 매개변수로 레퍼런스가 아닌 일반 클래스 타입을 받아야 한다.