본문 바로가기
코스웨어/16년 스마트컨트롤러

2016-09-26_조재찬_스터디일지_CPP-생성자와 소멸자

by 알 수 없는 사용자 2016. 9. 26.
728x90
반응형

생성자 (constructor)

: 클래스의 이름과 동일한 이름의 함수이면서,

반환형이 선언되지 않았고 실제 반환하지 않는 함수가 생성자


생성자는 객체 생성시 멤버변수의 초기화에 사용가능 

(객체 생성시 생성자가 딱 한번 호출)



- 위에서 생성자가 정의되었으니 객체 생성과정에서 자동호출되는 생성자에게 전달할 인자의 정보를 추가


SimpleClass sc(20) // 생성자에 20 전달 (stack 할당)

SimpleClass * ptr = new SimpleClass(30); // (heap에 동적 할당)



생성자도 오버로딩과 매개변수의 default값 설정이 가능하다.


생성자의 오버로딩

#include <iostream>
using namespace std;

class SimpleClass
{
	int num1;
	int num2;

public:
	SimpleClass()
	{
		num1=0;
		num2=0;
	}
	SimpleClass(int n)
	{
		num1=n;
		num2=0;
	}
	SimpleClass(int n1, int n2)
	{
		num1=n1;
		num2=n2;
	}

	void ShowData() const
	{
		cout<<num1<<' '<<num2<<endl;
	}
};

int main(void)
{
	SimpleClass sc1;
	sc1.ShowData();

	SimpleClass sc2(100);
	sc2.ShowData();

	SimpleClass sc3(100, 200);
	sc3.ShowData();

	return 0;
}


Output:

1
2
3
0 0
100 0
100 200



매개변수의 default 값 설정

#include <iostream>
using namespace std;

class SimpleClass
{
	int num1;
	int num2;

public:	
	SimpleClass(int n1=0, int n2=0)
	{
		num1=n1;
		num2=n2;
	}

	void ShowData() const
	{
		cout<<num1<<' '<<num2<<endl;
	}
};

int main(void)
{
	SimpleClass sc1;
	sc1.ShowData();

	SimpleClass sc2(100);
	sc2.ShowData();

	SimpleClass sc3(100, 200);
	sc3.ShowData();
	return 0;
}


Output:

1
2
3
0 0
100 0
100 200



클래스에 생성자가 하나도 정의되어 있지 않아도, 객체 생성시 생성자가 호출

-> 컴파일러에 의해 다음과 같은 default 생성자가 자동 삽입 

SimpleClass() { }


이는 new 연산자를 이용하는 객체의 생성에도 해당된다.


단, 다음과 같이 new연산자 대신 malloc 함수를 이용하면 생성자는 호출되지 않는다.

AAA * ptr = (AAA*)malloc(sizeof(AAA));    // AAA 클래스의 크기 정보만 바이트 단위로 전달



객체의 생성 예들

SimpleClass * ptr1=new SimpleClass();    (o)


SimpleClass * ptr1=new SimpleClass;    (o)


SimpleClass sc1;   (o)


SimpleClass sc1(); (x)

-> 이러한 형태의 객체 생성은 불가

컴파일러가 함수의 원형 선언인지 객체 생성인지 판단 불가

(sc1 함수이며, 인자가 void형이고 반환형이 SimpleClass인 함수라고 볼 수도 있다.)


다음예제와 같이 SimpleClass sc1()같은 유형의 문장은 함수의 원형선언으로 볼 수도 있으므로, 

이러한 유형의 문장은 객체 생성이 아닌 함수의 원형선언에만 사용가능하다.


#include <iostream>
using namespace std;

class SimpleClass
{
	int num1;
	int num2;

public:
	SimpleClass(int n1=0, int n2=0)
	{
		num1=n1;
		num2=n2;
	}

	void ShowData() const
	{
		cout<<num1<<' '<<num2<<endl;
	}
};

int main(void)
{
	SimpleClass sc1();    // 함수의 원형 선언!
	SimpleClass mysc=sc1(); // sc1 함수를 호출해, 이 때 반환되는 객체의 값으로 mysc 객체를 초기화
	mysc.ShowData();

	return 0;
}

SimpleClass sc1()
{
	SimpleClass sc(20, 30);
	return sc;
}


Output:

1
20 30



Member Initializer(멤버 이니셜라이저)를 이용한 멤버 초기화



위에서 Point 클래스에 생성자를 적용하고 객체를 생성하는 것은 무리가 없다.

하지만 Rectangle 클래스의 생성자 정의는 큰 문제가 있다. Rectangle 클래스는 두 개의 Point 객체를 멤버로 지니고 있기 때문이다. (upLeft, lowRight)


위의 클래스에서 생성자가 삽입되었으므로 default 생성자는 호출되지 않는다.


그렇다고 하여 void 생성자를 별도로 추가한다면? 생성자도 오버로딩이 되기 때문에 객체는 생성되겠지만 유효한 값을 전달받지 못하게 된다. (쓰레기 값)

따라서 Rectangle 객체가 생성될 때, 두 개의 Point 객체가 생성되며, 각 인자가 유효한 값을 전달받을 수 있는 생성자를 호출 할 수 있어야 한다.


이 문제 해결을 위해 '멤버 이니셜라이저'를 이용하면, 객체의 초기화를 수월하게 해줄 생성자를 호출할 수 있게 된다.


// 생성자가 추가된 Rectangle 클래스의 선언

1
2
3
4
5
6
7
8
9
class Rectangle
{
    Point upLeft;
    Point lowRight;
 
public:
    Rectangle(const int &x1, const int &y1, const int &x2, const int &y2);
    void ShowRecInfo() const;
};
cs


// Rectangle 클래스의 생성자 정의

1
2
3
4
5
Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2)
            :upLeft(x1, y1), lowRight(x2, y2)
{
    // empty
}
cs


다음의 문장이 '멤버 이니셜라이저'를 뜻하며, 이것을 통해 수월하게 멤버 초기화가 가능하다.

:upLeft(x1, y1), lowRight(x2, y2)


"객체 upLeft의 생성과정에서 x1, y1을 인자로 전달받는 생성자 호출,

객체 lowRight의 생성과정에서 x2, y2을 인자로 전달받는 생성자 호출"



범위지정 연산자 ::

: namespace외에도 명시적(explicitly)으로 클래스(구조체)멤버나 전역변수 지정



다음은 Point, Rectangle 클래스에 생성자를 추가한 예제이다.


 Point.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef __POINT_H_
#define __POINT_H_
 
class Point
{
    int x; 
    int y;    
public:
    Point(const int &xpos, const int &ypos);
    int GetX() const;
    int GetY() const;
    bool SetX(int xpos);
    bool SetY(int ypos);
};
 
#endif
cs


 Point.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include "Point.h"
using namespace std;
 
Point::Point(const int &xpos, const int &ypos)
{
    x=xpos;
    y=ypos;
}
 
int Point::GetX() const {return x;}
int Point::GetY() const {return y;}
 
bool Point::SetX(int xpos)
{
    if(0>xpos || xpos>100)
    {
        cout<<"벗어난 범위의 값 전달"<<endl;
        return false;
    }
 
    x=xpos;
    return true;
}    
bool Point::SetY(int ypos)
{
    if(0>ypos || ypos>100)
    {
        cout<<"벗어난 범위의 값 전달"<<endl;
        return false;
    }
 
    y=ypos;
    return true;
}
cs


 Rectangle.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef __RECTANGLE_H_
#define __RECTANGLE_H_
 
#include "Point.h"
 
class Rectangle
{
    Point upLeft;
    Point lowRight;
 
public:
    Rectangle(const int &x1, const int &y1, const int &x2, const int &y2);
    void ShowRecInfo() const;
};
 
#endif
 
cs


 Rectangle.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include "Rectangle.h"
using namespace std;
 
Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2)
            :upLeft(x1, y1), lowRight(x2, y2)
{
    // empty
}
 
void Rectangle::ShowRecInfo() const
{
    cout<<"좌 상단: "<<'['<<upLeft.GetX()<<", ";
    cout<<upLeft.GetY()<<']'<<endl;
    cout<<"우 하단: "<<'['<<lowRight.GetX()<<", ";
    cout<<lowRight.GetY()<<']'<<endl<<endl;
}
cs


 RectangleConstructor.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
#include "Point.h"
#include "Rectangle.h"
using namespace std;
 
int main(void)
{
 
    Rectangle rec(1155);
    rec.ShowRecInfo();
    return 0;
}
 
cs


Output : 

좌 상단: [1, 1]

우 하단: [5, 5]



멤버 이니셜라이저를 이용한 변수 및 const 상수(변수) 초기화
const는 변수를 상수화 하는 키워드이다. const 선언에 의해 상수화된 변수를 가리켜  'const 변수'라고도 하고 'const 상수'라고도 한다. 즉, 같은 의미


 객체가 아닌 멤버 변수의 초기화

: 이렇게 초기화 하는 경우 멤버 변수가 선언과 동시에 초기화


1
2
3
4
5
6
7
8
9
10
11
class SoSimple
{
private:
    int num1;
    int num2;
public :
    SoSimple(int n1, int n2) : num1(n1)
    {
        num2=2;
    }
};
cs

위의 클래스에서 생성자의 몸체에서 보인 초기화,

num2=2;


는 다음의 두 문장과 같다.

int num2;

num2=n2;


하지만 이니셜라이저를 이용한 다음의 초기화,

num1(n1)


는 다음의 문장과 같다.

int num1=n1;




따라서 이러한 특성을 이용해 const 멤버변수도 이니셜라이저를 이용해 초기화 가능하다.

#include <iostream>
using namespace std;

class FruitSeller
{
	const int APPLE_PRICE;
	int numOfApples;
	int myMoney;
public:
	FruitSeller(int price, int num, int money)
		: APPLE_PRICE(price), numOfApples(num), myMoney(money)
	{
	}
	int SaleApples(int money)  
	{
		int num=money/APPLE_PRICE;
		numOfApples-=num;
		myMoney+=money;
		return num;
	}
	void ShowSalesResult() const
	{
		cout<<"남은 사과: "<<numOfApples<<endl;
		cout<<"판매 수익: "<<myMoney<<endl<<endl;
	}
};

class FruitBuyer
{
	int myMoney;
	int numOfApples;
public:
	FruitBuyer(int money)
		: myMoney(money), numOfApples(0)
	{
	}
	void BuyApples(FruitSeller &seller, int money)
	{
		numOfApples+=seller.SaleApples(money);
		myMoney-=money;
	}
	void ShowBuyResult() const
	{
		cout<<"현재 잔액: "<<myMoney<<endl;
		cout<<"사과 개수: "<<numOfApples<<endl<<endl;
	}
};

int main(void)
{
	FruitSeller seller(1000, 20, 0);
	FruitBuyer buyer(5000);
	buyer.BuyApples(seller, 2000);
	
	cout<<"과일 판매자의 현황"<<endl;
	seller.ShowSalesResult();
	cout<<"과일 구매자의 현황"<<endl;
	buyer.ShowBuyResult();

	return 0;
}


Output:

1
2
3
4
5
6
7
8
과일 판매자의 현황
남은 사과: 18
판매 수익: 2000

과일 구매자의 현황
현재 잔액: 3000
사과 개수: 2


이렇게 생성자의 몸체에서 초기화 하는 방법 대신에 이니셜라이저를 이용한 변수 및 const 상수의 초기화 방법은 이 점이 있다.

const 멤버 변수를 이니셜라이저를 이용한 초기화가 가능한 거 외에도 다음의 두 가지 이점이 있다.


- 초기화 대상을 명확히 인식가능

- 성능에 약간의 이점



멤버 변수로 참조자 선언하기 (이니셜라이저를 이용)

이니셜라이저가 선언과 동시에 초기화 되는 특성을 이용해, 참조자의 초기화도 가능

1
2
3
4
5
6
7
8
9
10
11
class BBB
{
private:
    AAA &ref;
    const int &num;
 
public:
    BBB(AAA &r, const int &n) // 생성자
        :ref(r), num(n) // 멤버 이니셜라이저
    {    // 빈 생성자 몸체
    }
cs


private 생성자

: 클래스 내부에서만 객체의 생성을 허용


다음 예제에서 클래스 외부에서는 9행의 public 생성자를 기반으로 객체를 생성한다.


10행에서는 19행에 정의된 private 생성자 기반으로 AAA 객체를 생성 및 반환하고 있다.

이 떄, heap 영역에 생성된 객체를 참조의 형태로 반환하고 있다.


#include <iostream>
using namespace std;

class AAA
{
private:
	int num;

public:
	AAA() : num(0) {}
	AAA & CreateInitObj(int n) const
	{
		AAA * ptr=new AAA(n);
		return *ptr;
	}
	void ShowNum() const { cout<<num<<endl; }

private:
	AAA(int n) : num(n) {}
};

int main(void)
{
	AAA base;
	base.ShowNum();

	AAA &obj1=base.CreateInitObj(3);
	obj1.ShowNum();

	AAA &obj2=base.CreateInitObj(12);
	obj2.ShowNum();

	delete &obj1;
	delete &obj2;

	return 0;
}


Output:

1
2
3
0
3
12



소멸자 (Destructor)

: 객체 소멸시 호출되며 다음과 같은 형태를 갖는다.


클래스의 이름앞에 ~ 가 붙음

반환형이 선언되어 있지않고, 실제 반환도 없다.


default 생성자와 마찬가지로 소멸자를 정의하지 않았다면 아무런 일을 하지 않는 default 소멸자가 자동 삽입된다.


1
2
3
4
class AAA
{    
// empty class
};
cs

이는 다음의 클래스 정의와 100% 동일하다.


1
2
3
4
5
6
class AAA
{
public:
AAA() { }
~AAA() { }
};
cs


이러한 소멸자는 대게 생성자에서 할당한 리소스의 소멸에 사용된다.

다음 예제에서 new키워드를 이용해 동적할당한 메모리 공간을 소멸시키는 소멸자를 볼 수 있다.


13-14행 : 필요한 만큼 메모리 공간을 사용하기 위해 문자열의 길이만큼 메모리 공간을 동적 할당

23행 : 소멸자를 통해 할당된 메모리 공간을 소멸 시킴. delete 키워드를 이용하며 소멸되었음을 확인할 수 있는 출력문장이 삽입


#include <iostream>
#include <cstring>
using namespace std;

class Person
{
private:
	char * name;
	int age;
public:
	Person(char * myname, int myage)
	{
		int len = strlen(myname) + 1;
		name = new char[len];
		strcpy(name, myname);
		age = myage;
	}

	void ShowPersonInfo() const
	{
		cout << "이름: " << name << endl;
		cout << "나이: " << age << endl;
	}

	~Person()
	{
		delete[]name;
		cout << "called destructor!" << endl;
	}
};

int main(void)
{
	Person man1("Lee dong woo", 29);
	Person man2("Jang dong gun", 41);
	man1.ShowPersonInfo();
	man2.ShowPersonInfo();

	return 0;
}


Output :

이름: Lee dong woo

나이: 29

이름: Jang dong gun

나이: 41

called destructor!

called destructor!


728x90