[전공]

[명품 C++] 03 - 2 생성자 소멸자 구조체

danhan 2022. 6. 2. 11:08

출처 :

  • 명품 C++ Programming (저자 황기태)
  • 객체지향프로그래밍

생성자 constructor

  • 객체가 생성되는 시점에서 자동으로 호출되는 멤버 함수
  • 클래스 이름과 동일한 멤버 함수
  • 리턴 값이 없다.
class Circle {
  Circle();               // 클래스 이름과 동일
  Circle(int r);          // 생성자 중복 선언 - 매개 변수 없는 생성자, 매개 변수 가진 생성자
};

Circle::Circle() { }      // 생성자 함수도 구현해야 한다.
Circle::Circle(int r) { }

생성자 함수의 특징

  • 생성자의 목적
    • 객체가 생성될 때 객체가 필요한 초기화를 위해
    • 멤버 변수 값 초기화, 메모리 할당, 파일 열기, 네트워크 연결 등
  • 생성자 이름은 반드시 클래스 이름과 동일
  • 리턴 타입이 없다. void 타입도 안됨
  • 객체 생성 시 한 번만 호출
    • 자동으로 호출됨
    • 임의로 호출할 수 없음
    • 각 객체마다 생성자 실행됨
  • 생성자는 중복 가능
    • 한 클래스 내에 여러 개 가능
    • 중복된 생성자 중 하나만 실행됨
  • 생성자가 선언되어 있지 않으면 기본 생성자 자동으로 생성
    • 기본 생성자(디폴트 생성자)는 매개 변수 없는 생성자
    • 컴파일러에 의해 자동 생성
    • 생성자가 하나라도 선언된 클래스의 경우 기본 생성자를 자동 생성하지 않음
    • class Circle { public: int radius; double getArea(); Circle(int r); }; Circle::Circle(int r) { radius = r; } int main() { Circle donut; // 매개변수 없는 기본 생성자가 없으므로 컴파일 오류 }

위임 생성자 - 생성자가 다른 생성자 호출

  • 여러 생성자에 중복 작성된 코드의 간소화
  • 타겟 생성자 : 객체 초기화를 전담하는 생성자
  • 위임 생성자 : 타겟 생성자를 호출하는 생성자, 객체 초기화를 타겟 생성자에 위임.
// 위임 생성자
Circle::Circle() : Circle(1) { } // Circle(int r)의 생성자 호출 - r에 1 전달

// 타겟 생성자
CIrcle::Circle(int r) {
	radius = r;
}

생성자 멤버 변수 초기화 방법

// 생성자 코드에서 멤버 변수 초기화
Point::Point() { x = 0; y = 0; }
Point::Point(int a, int b) { x = a; y = b; }

// 생성자 서두에 초기값으로 초기화
Point::Point() : x(0), y(0) { }
Point::Point(int a, int b) : x(a), y(b) { }

// 클래스 선언부에서 직접 초기화
class Point {
	int x = 0, y = 0;
};

소멸자

  • 객체가 소멸되는 시점에서 자동으로 호출되는 함수
    • 한 번만 자동 호출
    • 임의로 호출할 수 없음
    • 객체 메모리 소멸 직전 호출됨
class Circle {
	Circle();
  Circle(int r);
	.....
	~Circle();          // 소멸자는 오직 하나만 존재
};

Circle::~Circle() { } // 소멸자 함수도 구현해야 한다.

소멸자 특징

  • 소멸자의 목적
    • 객체가 사라질 떄 마무리 작업을 위해
    • 실행 도중 동적으로 할당 받은 메모리 해제, 파일 저장 및 닫기, 네트워크 닫기 등
  • 소멸자 함수의 이름은 클래스 이름 앞에 ~
  • 리턴 타입이 없고 어떤 값도 리턴하면 안됨
  • 중복 불가능
    • 한 클래스 내에 오직 한 개만 작성 가능
    • 소멸자는 매개 변수 없는 함수
  • 소멸자가 선업되어 있지 않으면 기본 소멸자가 자동 생성
    • 컴파일러에 의해 기본 소멸자 코드 생성
    • 컴파일러가 생성한 기본 소멸자 : 아무 것도 하지 않고 단순 리턴
    class Circle {
    public:
    	int radius;
    	double getArea();
    	Circle(int r);
    	~Circle();
    };
    
    Circle::Circle(int r) {
    	radius = r;
    }
    
    Circle::~Circle() {
    	cout << "반지름" << radius << endl;
    }
    
    int main() {
    	Circle pizza;
    	
    	return 0;  // main() 함수가 종료하면 main 함수 스택에 생성된 pizza 객체가 소멸됨
    }
    

생성자/소멸자 실행 순서

  • 객체가 선언된 위치에 따른 분류
    • 지역 객체 - 함수 내에 선언된 객체로서, 함수가 종료하면 소멸
    • 전역 객체 - 함수 바깥에 선언된 객체로서, 프로그램이 종료할 때 소멸
  • 객체 생성 순서
    • 전역 객체는 프로그램에 선언된 순서로 생성
    • 지역 객체는 함수가 호출되는 순간에 순서대로 생성
  • 객체 소멸 순서
    • 함수가 종료하면, 지역 객체가 생성된 순서의 역순으로 소멸
    • 프로그램이 종료하면, 전역 객체가 생성된 순서의 역순으로 소멸
  • new를 이용하여 동적으로 생성된 객체의 경우 - 동적으로 할당받은 메모리는 heap에 저장
    • new를 실행하는 순간 객체 생성
    • delete 연산자를 실행할 떄 객체 소멸
// 전역 객체 생성
Circle globalDonut(1000);  // 1
Circle globlaPizzas(2000); // 2

void f() {
	// 지역 객체 생성
	Circle fDonut(100);     // 5
	Circle fPizza(200);     // 6
}

int main() {
	// 지역 객체 생성
	Circle mainDonut;       // 3
	Circle mainPizza(30);   // 4
	f();
}

접근 지정자

캡슐화의 목적

  • 객체 보호, 보안
  • C++ 에서 객체의 캡슐화 전략
    • 객체의 상태를 나타내는 데이터 멤버(멤버 변수)에 대한 보호
    • 중요한 멤버는 다른 클래스나 객체에서 접근할 수 없도록 보호
    • 외부와의 인터페이스를 위해서 일부 멤버는 외부에 접근 허용

멤버에 대한 3가지 접근 지정자

  • private - 동일한 클래스의 멤버 함수에만 제한
  • public - 모든 다른 클래스에 허용
  • protected - 클래스 자신과 상속받은 자식 클래스에만 허용

→ 클래스 디폴트 접근 지정은 private

  • 멤버 변수는 private 지정이 바람직

함수 호출에 따른 시간 오버헤드

  • 함수 호출 → 돌아올 리턴 주소 저장 → cpu 레지스터 값 저장 → 함수의 매개 변수를 스택에 저장 → 함수 실행 → 함수의 리턴 값을 임시 저장소에 저장 → 저장한 레지스터 값 CPU에 복귀 → 돌아갈 주소를 알아내어 리턴
  • 작은 크기의 함수를 호출하면, 함수 실행 시간에 비해 호출을 위해 소요되는 부가적인 시간 오버헤드가 상대적으로 크다
  • 해결 방법 : 인라인 함수

인라인 함수

  • inline 키워드로 선언
  • 인라인 함수를 호출하는 곳에 인라인 함수 코드를 확장 삽입
    • 매크로와 유사
    • 코드 확장 후 인라인 함수는 사라짐
  • 인라인 함수 호출
    • 함수 호출에 따른 오버헤드가 존재하지 않음
    • 장점 - 프로그램의 실행 속도 개선
    • 단점 - 컴파일된 전체 코드 크기 증가
  • 컴파일러에 의해 이루어짐
  • C++ 프로그램의 실행 속도 향상 - 자주 호출되는 짧은 코드의 함수 호출에 대한 시간 소모를 줄임
  • 제약 사항 - recursion, 긴 함수, static 변수, 반복문, switch문, goto문 등을 가진 함수는 수용하지 않음

자동 인라인 함수

  • 클래스 선언부에 구현된 멤버 함수
  • inline으로 선언할 필요 없음
  • 컴파일러에 의해 자동으로 인라인 처리
  • 생성자를 포함, 모든 함수가 자동 인라인 함수 가능
// 멤버 함수를 inline으로 선언하는 경우
class Circle {
private:
	int radius;
public:
	double getArea();
	Circle();
};

inline Circle::Circle() {
	radius = 1;
}

inline double Circle::getArea() {
	return 3.14 * radius * radius;
}

// 자동 인라인 함수로 처리되는 경우
class Circle {
private:
	int radius;
public:
	double getArea() {
		return 3.14 * radius * radius;
	}
	Circle() {
		radius = 1;
	}
};

C++ 구조체

  • 상속, 멤버, 접근 지정 등 모든 것이 클래스와 동일
  • 유일하게 다른 점
    • 구조체의 디폴트 접근 지정 - public ( C언어와의 호환성 : C는 접근지정 개념 자체가 없음)
    • 클래스의 디폴트 접근 지정 - private
  • C++에서 구조체를 수용한 이유?
    • C의 구조체 100% 호환
    • C의 소스를 그대로 가져다 쓰기 위해
  • struct 키워드 생략

바람직한 C++ 프로그램 작성법

클래스를 헤더 파일과 cpp 소스 파일로 분리 클래스 재사용

  • 클래스마다 분리 저장
  • 클래스 선언 부 → 헤더파일 .h에 저장
  • 클래스 구현 부 → 소스파일 .cpp에 저장 + 클래스가 선언된 헤더 파일 include
  • main() 등 전역 함수나 변수는 다른 cpp 파일에 분산 저장
// Circle.h - 단순 선언
class Circle {
pivate:
	int radius;
public:
	Circle();
	Circle(int r);
	double getArea();
};

// Circle.cpp - Circle 클래스 구현부
#include <iotream>
#include "Circle.h"
using namespace std;

Circle::Circle() {
	radius = 1;
}

double Circle::getArea() {
	return 3.14 *radius;
}

// main.cpp - main 클래스
#include <iotream>
#include "Circle.h"
using namespace std;

int main() {
	Circle donut;
	double area = donut.getArea();
}

헤더 파일의 중복 include 문제

조건 컴파일문으로 해결

#include <iotream>
#include "Circle.h"
#include "Circle.h" // 중복 include하면 컴파일 오류
using namespace std;
#ifdef CIRCLE_H
#define CIRCLE_H

class Circle {
private:
	int radius;
public:
	Circle();
	Circle(int r);
	double getArea();
};

#endif

실습 예제 - 헤더 파일과 cpp 파일 분리

// Adder.h
#ifndef ADDER_H
#define ADDER_H

class Adder {
	int op1, op2;
pulic:
	Adder(int a, int b);
	int procdess();
};

#endif

// Adder.cpp
#include "Adder.h"

Adder::Adder(int a, int b) {
	op1 = a;
	op2 = b;
}

int Adder::proocess() {
	return p1 + op2;
}