본문 바로가기
C++

#1. 왜 C++인가? / 생성자와 소멸자

by imagineer_jinny 2022. 1. 25.
  • C와 C++ 차이
    • C : 절차지향 / C++ : 객체지향(클래스 기반 문법들 많이 제공)
    • 클래스 = 틀 = 자료형(int, float, double)
      • 즉, 클래스란 객체(Object) 사용자 정의 자료형
        • 클래스: 붕어빵 틀
        • 객체: 붕어빵

 

  • 게임이란?
    • 객체들간의 상호 작용을 화면에 보여주는 것 (카트라이더에서 카트 class 객체들)

 

  • C++ 쓰는 이유 중 하나 : 성능
    • 성능: 속도 / 메모리
      • 속도 : 비슷함
      • 메모리 : 메모리 공간을 효율적으로 사용(낭비가 적음)
        • 게임 설치할 때 메모리 부족하다고 뜨면 어차피 게임이 안 깔리니까 못함. 설치한 후 게임 도중 메모리가 부족하다고 뜨면 크래시 남. 메모리가 동적으로 변동됨. 동적으로 생성/해제 빈번
        • 시간 복잡도(실행 시간), 공간 복잡도(메모리 공간)

 

  • 생성자(Constructor)
    • 객체가 생성될 때 필드나 여러 절차들을 초기화하는 함수
    • 접근 제어자가 반드시 public으로 선언되어야 한다. 
    • return 타입이 없다. 즉, 반환값이 없다.
    • 오버로딩(Overloading)을 통해 중복정의가 가능하다.
    • 객체가 생성될 때 자동으로 생김

 

  • 디폴트 생성자(Default Constructor)
    • 매개변수가 없거나 초기화된 매개변수(defalut parameter)를 가진 생성자 
    • default 생성자의 문제
      • 기본값으로만 초기화 해줌
    • class Test { int score; public: Test() {} // default constructor }

 

    • 초기화 리스트(Initialization List)
      • 생성자에서 필드를 간단하게 초기화하는 방법
        class Test 
        {
        	int score;
        public:
        	Test(int s = 10) : score(s) {}
        }​

 

    • 초기화 리스트 사용 이유?
      1. 필드에 const로 선언된 변수(상수)를 초기화하는 경우
      2. 필드에 선언된 레퍼런스 변수를 초기화하는 경우
        • 이 두가지 경우 모두 생성과 동시에 초기화되어야하는 변수들이기 때문에 초기화 리스트에 넣어야 함
    • { } 이 안은 생성이 된 이후라서 안됨 

 

    • 소멸자(Destructor)
      • 소멸자객체가 소멸될 때 자동으로 실행되는 클래스의 멤버 함수
      • 생성자는 클래스의 초기화를 돕도록 설계됐지만 소멸자는 청소를 돕도록 설계됨
        1. 소멸자 이름은 클래스 이름과 같아야 하고 앞에 ~를 달아야 한다.
        2. 소멸자는 인수가 없다.
        3. 소멸자는 반환 값이 없다.

 

    • 참조(Reference)
      • 사용 이유?
        • 레퍼런스 변수를 사용하는 이유는 메모리를 절약하기 위해서. 레퍼런스 변수로 선언하면 따로 메모리를 잡지 않고 해당 변수에 접근할 수 있기 때문에 쓸데없는 메모리를 잡지 않게됨
      • 레퍼런스 변수는 변수 메모리 자체가 따로 잡히는 것이 아니라 값을 담고있지는 않음
      • 별명이 하나 생기는 것
        • int&num1 = num2; 라고 하는순간 num2에 해당하는 메모리 공간에 num1이라는 이름을 하나 더 붙여주는 것

 

    • 생성, 소멸자 왜 쓰나?
      • Memory Leak 최소화 하기 위해. 성능적으로 놓치는 것 없게 하기 위해
      • RAII : 리소스의 수명은 생성/소멸자로 결정된다 (면접질문가능)
      • 개체의 생성, 소멸은 프로그래머가 직접 관리해야 한다


      • 복사 대입 연산자
        • 같은 타입의 객체를 이미 생성되어 있는 객체에 값을 복사할 때 사용

 

  • 메모리 구조
    • 힙 영역 : Only 동적 할당으로 만든 변수들만 쌓임. 
      • 왜 써? 스택이 작으니까. 스택은 비워놓는 게 좋음. 공간 작음
      • 전역변수 쓰면 되잖아? 
        • 전역변수 단점: 프로그램 끝날 때까지 메모리 잡혀 있음
          • 잘 안쓰고 메모리 낭비
      • 동적할당으로 만든 변수들 특징: 이름이 없음. 메모리 할당 후 주소값을 리턴해줌.
      • 주소값 보관 변수 = pointer
      • int* ptr= new int;  //여기서 new는 함수임
    • 프로그래머가 요청할 때만 할당이 됨
    • 공간도 스택보다 큼

코딩의 시작, TCP School

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

  • 포인터(pointer)
    • 메모리의 주소를 가지고 있는 변수
    • 주소 값을 통한 메모리 접근을 한다(간접 참조)
    • 포인터 변수는 다 4 byte!!!!
    • 왜 써?
      • 예를 들어 Animal이라는 클래스에 지역변수로 스택에 30 byte를 쓴다고 가정하자.
      • 근데 포인터 변수는 4 byte라고 했지.
      • Animal * ptr (4 byte) = new Animal (30 byte)
      • 다 묶어서 포인터변수로 부르면 4byte로 메모리 save
      • 쓰는 이유 하나 더! 포인터는 이름 없는 애 가리킬 수 있어. 주소로!
      • null ptr은 가리키는 게 없음. 값이 들어가야 화살표가 생기는 거니까

 

포인터 변수는 4byte 잡아 먹음. 근데 Reference 변수는 그것 조차 안하겠다는 것임

 

  • 레퍼런스(reference)
    • 레퍼런스 = 참조자 (C++ 문법)
    • 참조자는 자신이 참조하는 변수를 대신할 수 있는 또 하나의 이름이다.
    • 즉, 변수에 별명(별칭)을 하나 붙여주는 것
    • 변수 명을 통해서 메모리를 참조한다. (직접 참조)

 

    • 참조 대상 변경 불가

 

예를 들어 포인터 같은 경우에는 덮어씌우기 가능. 

그리고 선언과 동시에 초기화 안해줘도 됨.

 

<포인터>

 

<참조>

한번 정해주면 다른 별명으로 못 바꿈. 

그래서 생성과 동시에 초기화.

int & num; (X) 별명 대상이 있어야 함.

num과 ref 같은 경우 대상 한번 정해지면 한몸. ref==num임.

 

그래서 ref가 num2의 별명이 되는게 아니라 (ref는 위에서 num의 별명으로 갔으니까)

대입이 되어 버리는 것임.

 

그렇다면 참조의 한계는?

선언과 동시에 초기화 해야 한다는 것

 

 

 

 

 * call by value : 

Swap(int a, int b) 여기에서 메모리를 따로 잡기 때문에 지역변수 값(int a, int b)만 바뀌고 실제 값이 안바뀌는 것임.

참조는 메모리를 따로 안잡고 main에 직접 들어간 거라서 값이 바뀜. 

 

  • 복사 생성자
    • 객체를 인자로 넣을 때 복사생성자가 불린다
  • 자동 복사 생성자의 문제점?
    • 단순히 대입만 하기 때문에 포인터가 있을 경우 주소 값만 바꿔준다는 문제가 있음 (얕은복사 문제)

 

 

 

- 함수들은 메모리에 없음

- 변수에 접근하려면 1)이름이 있거나 2)주소가 있어야 함

- 동적 할당 하는 이름이 없어서 포인터로 접근함

답은 10, 10 이 나옴.

 

cout 까지 출력된 이후

return 이후에 지역변수인 a, b가 소멸될 때가 문제인데,

생성 순서와 반대로 소멸됨(스택 구조 참고)

~CMyData b(a)

~CMyData a(10) 

순서로 소멸되는데 ~CMyData b(a) 과정에서

b의 복사 생성자인 a가 불리는데 

이때 위의 코드에서는 따로 복사생성자를 생성하지 않았기 때문에

자동으로 복사생성자가 불리는데 이 복사생성자는 단순히 a의 값을 복사(대입)만 하는 형태임.

이 때 포인터도 그대로 대입하게 되는데 같은 포인터를 가리키게 되고

메모리 해제 할 때 ~CMyData b(a)가 가리키는 포인터를 해제했고

~CMyData a(10)에서 메모리 해제하려 했더니 헤제할 메모리가 없는 상황이 발생

 

 

  • 얕은 복사, 깊은 복사 (시험 많이 나옴)

 

 

  • 복사생성자는 얕은 복사에서는 crash가 나기 때문에 복사 생성자에서는 깊은 복사를 해야 함

 

  • const는 원본 안바꾸기 위해서 붙여주고 &가 없으면 무조건 메모리를 잡기 때문에 무한으로 객체를 부를 수 있음.
  • 생성자가 뭘 부를지는 ( 괄호 안에서 결정하는데, 
    • b(a 의 경우 a를 부르는 중에 a도 animal 부르고... 무한 반복! -> Crash!

위 긴 코드의 문제는 얕은복사가 아니라(얕은복사가 무조건 나쁜게 아님)

바로 위의 복사 생성자를 따로 생성해주지 않아서 문제가 생기는 것임.

위 코드 처럼 복사 생성자를 만들 때 메모리를 새로 할당 해주고, 포인터가 가리키는 위치에 값을 복사해줘야 함!

 

 

'C++' 카테고리의 다른 글

[C++] 개체지향 프로그래밍 / OOP, 접근 제어자, 스택/힙, new/delete  (0) 2022.02.05
#2. 복사 생성자의 호출 시점  (0) 2022.02.04
[C++] 참조(Reference)  (0) 2022.01.22
[C++] Bool 데이터형  (0) 2022.01.21
[C++] 입력(Input)  (0) 2022.01.21

댓글