[C++] 클래스(class)와 객체(object) 2편: 생성자(constructor)

HY K·2024년 8월 22일

명품 C++ 프로그래밍

목록 보기
12/24

지난번에 이어서 클래스에 대해서 계속 공부해보자.
이번에는 생성자에 대해서 본격적으로 다뤄볼 것이다


1. 생성자

클래스는 객체가 생성될 때 자동으로 실행되는 생성자(constructor)라는 멤버 함수를 통해서 객체를 초기화 할 수 있다. 여러 생성자를 작성할 수 있지만, 실행되는 것은 오직 하나 뿐이다.

생성자의 특징을 정리하면 다음과 같다.

  1. 초기화를 위해서 사용한다.
  2. 오직 한번만, 객체가 생성될 때만 실행된다.
  3. 생성자 함수의 이름은 반드시 클래스 이름과 같아야 한다.
  4. 생성자 함수의 원형은 리턴 타입을 선언하면 안된다. void조차도 안된다. 만약 리턴 타입을 명시할 경우 컴파일 오류가 발생한다.
  5. 마찬가지로, 어떠한 값도 리턴해서는 안된다. 종료를 위해서 return 문은 사용할 수 있다.
  6. 중복으로 선언이 가능하다. 단, 매개변수 개수, 타입은 모두 달라야 한다.

예시 코드와, 주석을 통한 설명을 통해 살펴보자.

#include <iostream>
using namespace std;

class Circle {
public:
	int radius; // 멤버 변수 선언
	double getArea();
	Circle(); // 생성자 함수
	Circle(int r); // 생성자 함수2
};

Circle::Circle() { // 생성자 함수 구현
	radius = 1;
	cout << radius << endl;
}

Circle::Circle(int r) { // 생성자 함수2 구현
	radius = r;
	cout << radius << endl;
}

double Circle::getArea() { // 멤버 함수
	return 3.14 * radius * radius;
}

int main() {
	Circle donut;
	double area = donut.getArea();
	cout << area << endl;

	Circle pizza(30);
	area = pizza.getArea();
	cout << area << endl;
}

객체가 생성되는 과정은 다음과 같다.
1. 객체 크기에 해당하는 메모리 공간 할당
2. 객체 내의 생성자 함수 실행을 통해 객체 초기화

위에는 서로 다른 매개변수를 입력받는 생성자 함수를 2개 구현하여, 보다 다양한 객체를 생성하도록 지원하고 있다.

2. 위임 생성자(delegating constructor), 생성자의 타 생성자 호출

어떠한 클래스의 생성자들에는 대개 객체를 초기화 하는 비슷한 코드들이 중복될 수 박에 없다. 이를 조금 더 효율적으로 처리하기 위해, C++11부터는 중복된 생성자 함수를 하나로 통폐합 하고, 다른 생성자에서 이 생성자를 호출할 수 있게 되었다. 예를 들면 다음과 같다.

Circle::Circle() : Cirlce(1) {} // Circle(int r)의 생정사 호출

Circle::Circle(int r){
	radius = r;
    cout<<radius<<endl;
}

여기서 Circle() 생성자가 호출되면, Circle() 생성자는 자신의 코드를 실행하기 전에 Circle(int r) 생성자를 호출하여, 해당 초기화 작업을 수행한다.

이처럼 객체의 초기화 작업이 구현된 생성자를 타겟 생성자(traget constructor), 객체의 초기화를 다른 생성자에게 위임하는 생성자를 위임 생성자(delegating constructor)라고 부른다. 타겟 생성자로부터 위임 생성자로 객체 초기화를 이관함으로써 객체 생성 과정이 명료해지고, 간단해지게 된다.

예시 코드는 다음과 같다.

#include <iostream>
using namespace std;

class Circle {
public:
	int radius; // 멤버 변수 선언
	double getArea();
	Circle(); // 생성자 함수
	Circle(int r); // 생성자 함수2
};

Circle::Circle() : Circle(1) {
 // 위임 생성자에게 넘겨주는 역할만 하기
}

Circle::Circle(int r) { // 생성자 함수2 구현
	radius = r;
	cout << radius << endl;
}

double Circle::getArea() { // 멤버 함수
	return 3.14 * radius * radius;
}

int main() {
	Circle donut;
	double area = donut.getArea();
	cout << area << endl;

	Circle pizza(30);
	area = pizza.getArea();
	cout << area << endl;
}

3. 생성자와 멤버 변수 초기화

클래스의 멤버 변수들을 생성자로부터 초기화 할 수 있다. 물론 멤버 변수들 그 자체로도 선언부 내부에서 초기화가 가능하다(C++11부터만).

예시를 들어보자.

class Point {
	int x,y;
public:
	Point();
    Point(int a, int b);
};

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) {}

이런 식으로, 중괄호 내부에 넣지 않고, 구현부에 멤버 변수와 초기값을 콤마(,)를 통해서 나열함으로써 손쉽게 초기화가 가능하다.

생성자를 활용한 멤버 변수 초기화 코드를 작성해보자.

#include <iostream>
using namespace std;

class Point {
	int x, y;
public:
	Point();
	Point(int a, int b);
	void show() { cout << "(" << x << ", " << y << ")" << endl; }
};

Point::Point() : Point(0, 0) {}
Point::Point(int a, int b) : x(a), y(b) {}

int main() {
	Point origin;
	Point target(10, 20);
	origin.show();
	target.show();
}

그렇게 어렵지 않다.

3. 만약 생성자가 없다면?

C++ 컴파일러는 생성자 중 하나를 반드시 호출하도록 컴파일을 하므로, 생성자를 선언하지 않은 클래스는 이론상 컴파일 오류를 발생시켜야 한다. 하지만 실제로는 컴파일 오류를 발생시키지 않는다.

그 이유로는, C++ 컴파일러가 생성자가 없는 클래스에 대해서 기본 생성자(default constructor)를 만들어서 자동으로 삽입하고, 기본 생성자가 호출되로록 컴파일 하기 때문이다.

3.1 기본 생성자

클래스에 생성자가 작성되어 있지 않을 때, 컴파일러가 자동으로 생성해주는 생성자로써, 디폴트 생성자라고 부르기도 한다. 어떠한 매개변수도 가지고 있지 않은 상태로 생성된다. 반대로, 생성자가 하나라도 선언된 클래스의 경우 컴파일러는 기본 생성자를 자동으로 집어넣지 않는다. 그렇기에, 이런 경우도 가능하다. 예를 들어보자.

#include <iostream>
using namespace std;

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

Circle::Circle(int r){
	radius = r;
};

만약 클래스를 위와 같이 작성했다고 가정해보자.
그러면 다음과 같이 작성하면 컴파일 오류 없이 실행된다.

Circle pizza(30);

하지만 다음과 같이 작성해서 빌드하면 컴파일 오류가 발생한다.

Circle donut;

왜냐면 이미 Circle(int r)이라는 생성자가 존재하기 때문에, Circle()에 해당하는 기본 생성자를 컴파일러가 따로 넣어주지 않기 때문이다. 따라서 Circle(int r)과 합치하지 못하는 Circle donut; 코드는 컴파일 오류를 불러일으킬 수 밖에 없다.

마지막으로 예제 코드를 하나 보고, 생성자 파트를 마무리 하도록 하자.

#include <iostream>
using namespace std;

class Rectangle {
public:
	int width, height;
	Rectangle();
	Rectangle(int w, int h);
	Rectangle(int lenght);
	bool isSquare();
};

Rectangle::Rectangle() {
	width = height = 1;
}

Rectangle::Rectangle(int w, int h) {
	width = w;
	height = h;
}

Rectangle::Rectangle(int length) {
	width = height = length;
}

bool Rectangle::isSquare() {
	if (width == height) return true;
	else return false;
}

int main() {
	Rectangle rect1;
	Rectangle rect2(3, 5);
	Rectangle rect3(3);

	if (rect1.isSquare()) cout << "1 is true" << endl;
	if (rect2.isSquare()) cout << "2 is true" << endl;
	if (rect3.isSquare()) cout << "3 is true" << endl;
}
profile
로봇, 드론, SLAM, 제어 공학 초보

0개의 댓글