생성자 (Constructors)
특수한 멤버 함수
객체가 생성될 때 자동으로 호출됨
초기화 목적으로 유용하게 사용됨
클래스와 동일한 이름을 갖는 멤버 함수
반환형은 존재하지 않음
오버로딩 가능
class Player{
public:
Player();
Player(std::string name);
Player(std::string name, int health, int xp);
//생성자들 반환형이 없고, 함수 이름이 클래스 이름과 동일함
private:
std::string name;
int health;
int xp;
};
소멸자 (Destructor)
특수한 멤버 함수
객체가 소멸할 때 자동으로 호출됨
메모리 및 기타 리소스(파일 close등) 해제 목적으로 유용하게 사용됨
클래스와 동일한 이름 앞에 ‘~’을 갖는 멤버 함수
반환형 및 파라메터는 존재하지 않음
오버로딩 불가능
class Player{
public:
Player();
Player(std::string name);
Player(std::string name, int health, int xp);
//생성자들 반환형이 없고, 함수 이름이 클래스 이름과 동일함
~Player();
//소멸자 ~로 시작하며 클래스와 이름이 동일
private:
std::string name;
int health;
int xp;
};
객체 또는 객체의 포인터가 소멸되는 시점에 자동으로 호출
{
Player slayer;
Player kim{"Kim", 100, 4};
Player hero{"Hero"};
Player enemy{"Enemy"};
//4 destructor called
}
Player *enemy = new Player("Enemy2", 1000, 0};
delete enemy; // desturctor called
기본 생성자 (Default Constructor)
인자가 없는 생성자
클래스의 생성자를 직접 구현하지 않으면, 컴파일러가 기본적으로 만들어줌
→ 초반부에, 생성자가 없는 상태에서도 객체를 만들 수 있었다.
→ 컴파일러가 기본 생성자를 알아서 만들어서 사용했기 때문
객체를 인자없이 생성하면 호출된다.
class Player{
}; // 이렇게 코드를 작성해도
class Player{
Player() //컴파일하면 자동으로 생성자가 하나 생김
{
}
};
Declaring a Class
인자가 없는 클래스 생성자도 구현 해 주는 것이 좋다.
쓰레기 값은 항상 방지하는 것이 안전
class Account
{
public:
Account(){
name = "None";
balance = 0.0;
}
bool Withdraw(double amount);
bool Deposit(double amount);
private:
std::string name;
double balance;
};
인자가 있는 생성자만 구현한 경우
기본 생성자가 자동 생성되지 않음
생성자 오버로딩 (Constructor Overloading)
생성자는 오버로딩 가능
각각의 생성자는 고유해야 함(매개변수가 달라야 함)
→ 함수 오버로딩과 동일한 규칙
class Player{
public:
Player();
Player(std::string nameVal);
Player(std::string nameVal, int healthVal, int xpVal);
private:
std::string name;
int health;
int xp;
};
생성자 초기화 리스트 (Constructor Initialization List)
이전의 생성자는 생성자 본체(body) 내에 멤버 변수 값을 대입
생성자 초기화 리스트를 사용할 경우 생성과 동시에 값이 지정됨
Player::Player(){
//이 시점에서는 멤버에 쓰레기 값이 들어 있다.
name = "None"; //assignment
health = 0;
xp = 0;
}
//권장 방법 생성자 멤버 초기화 리스트
Player::Player(){
:name{"None"}, health{0}, xp{0}
{
}
생성자 위임 (Delegating Constructors)
다양한 생성자의 오버로딩에 유사한 코드가 반복적으로 사용
오류의 가능성이 높아짐
생성자 위임을 통해 오류 가능성과 코드 반복을 줄일 수 있음
→ 다른 생성자를 멤버 초기화 리스트 위치에서 호출
→ 생성자 멤버 초기화 리스트를 사용해서만 가능
생성자 위임을 사용하지 않는 기존 코드
Player::Player(std::string nameVal, int healthVal, int xpVal){
:name{nameVal}, health{healthVal}, xp{xpVal}
{
}
Player::Player()
:name{"None"}, health{0}, xp{0}
{
}
Player::Player(std::string nameVal){
:name{nameVal}, health{0}, xp{0}
{
}
생성자 위임을 사용한 코드
Player::Player(std::string nameVal, int healVal, int xpVal)
:name{nameVal}, health{healthVal}, xp{xpVal}
{
}
Player::Player()
:Player{"None", 0, 0}
{
}
Player::Player(std::string nameVal){
:Player{nameVal, 0, 0}
{
}
생성자 기본 매개변수 (Default Constructor Parameters)
생성자 또한 함수이므로, 기본 매개변수 사용 가능
class Player{
public:
Player(std::string nameVal="None", int healthVal = 0, int xpVal = 0);
private:
std::string name;
int health;
int xp;
};
Player::Player(std::string nameVal, int healthVal, int xpVal){
:name{nameVal}, health{healthVal}, xp{xpVal}
{
}
복사 생성자 (Copy Constructor)
객체가 복사될 때는, 기존 객체를 기반으로 새로운 객체가 생성됨
객체가 복사되는 경우
→ 1.객체를 pass by value 방식으로 함수의 매개변수로 전달할 때
void display_player(Player P){
// p is a copy of hero
// use p
} // destructor for p is called
Player hero{0, 0, 1};
display_player(hero);
→ 2.함수에서 value의 형태로 결과를 반환할 때
Player create_super_enemy(){
Player an_enemy{1, 1, 1};
return an_enemy; //copy of an_enemy is returned
} //destructor for an_enemy is called
Player enemy;
enemy = create_super_enemy();
→ 3.기존 객체를 기반으로 새로운 객체를 생성할 때
Player hero{1,1,1};
Player another_hero = hero; //another hero is copy of hero
// or Player another_hero {hero};
객체가 어떤 방식으로 복사될 지 정의해 주어야 함
→ 사용자가 구현하지 않는 경우 컴파일러에서 자동으로 복사 생성자를 정의함
중요한 이유
복사 과정에서 문제가 발생할 수 있다. (포인터가 존재하는 경우)
복사 비용에 대한 인식(클래스는 많은 데이터를 포함할 수 있다.)
자동으로 생성되는 복사 생성자
사용자가 복사 생성자를 구현하지 않으면, 기본 복사 생성자가 사용
멤버 변수들의 값을 복사하여 대입하는 방식
포인터 타입의 멤버 변수가 존재할 때는 주의
→ 기본 복사 생성자는 포인터 타입의 변수 또한 복사하여 대입됨
→ 즉, 포인터가 가리키는 데이터의 복사가 아닌 포인터 주소값의 복사
얕은 복사(shallow copy) vs 깊은 복사(deep copy)
복사 생성자의 선언 (Declaring Copy Constructor)
동일한 타입의 const 참조자가 인자인 생성자
Player::Player(const Player &source);
Account::Account(const Account &source);
//ex 2
#include <iostream>
class Player{
public:
Player(int hp, int xp)
:hp{hp}, xp{xp}
{
std::cout << "생성자 호출됨" << '\n';
}
//복사 생성자
Player(const Player& source)
:hp{source.hp}, xp{source.xp}
{
std::cout << "복사 생성자 호출됨" << '\n';
}
void Print(){
std::cout << hp << ' ' << xp << std::endl;
}
private:
int hp;
int xp;
};
void PrintInformation(Player p){
p.Print();
}
int main(){
Player p{10, 2};
PrintInformation(p);
}
//result
//생성자 호출됨
//복사 생성자 호출됨
//10 2
얕은 복사 생성자의 구현 (Implementing Copy Constructor)
자동 생성된 복사 생성자의 행동
생성자 초기화 리스트를 사용한 방법
Player(const Player &other){
:x{other.x}, y{other.y}, speed{other.speed}
{
}
대입을 사용한 방법
Player(const Player &other){
x = other.x;
y = other.y;
speed = other.speed;
}
얕은 복사의 문제
class Shallow
{
private:
int *data; //pointer
public:
Shallow(int d); // Constructor
Shallow(const Shawllow &source); //Copy Constructor
~Shallow();
}; // 클래스의 정의
Shallow:Shallow(int d){
data = new int; //allocate storage
*data = d;
} // 생성자
Shallow:~Shallow(){
delete data; //free storage
cout << "free storage" << endl;
} //소멸자
포인터가 가리키는 데이터가 아닌 포인터 주소값의 복사
int main(){
Shallow_obj1{100};
display_shallow(obj1); //copy of obj1 is release data
return 0;
} //obj1 try to release data again!
아래와 같이 구현된 경우 얕은 복사
Shallow:Shallow(const Shallow &source){
:data{source.data}
//source 객체의 data에 저장된 주소값이 현재 객체의 data에 복사
{
cout << "Copy constructor, shallow" << endl;
}
//ex 3
int main(){
int* p1 = new int;
*p1 = 100;
int* p2 = p1;
delete p2;
delete p1; // exception error
//delete를 안하면 메모리 누수 발생
깊은 복사
포인터 주소를 복사하는 것이 아니라, 데이터를 복사하여 복사 생성
복사 생성자가 새로운 힙 공간을 할당하여 동일한 데이터 생성
class Deep{ // 클래스 정의
private:
int *data; // pointer
public:
Deep(int d); // constructor
Deep(const Deep &soucre); // copy constructor
~Deep();
};
//생성자
Deep::Deep(int d){
data = new int; // allocate storage
*data = d;
}
//소멸자
Deep::~Deep(){
delete data; // free storage
cout << "free storage" << endl;
}
//깊은 복사 생성자
Deep::Deep(const Deep *source){
data = new int; // allocate new storage
*data =*source.data; //dereferencing
cout << "Copy constructor, deep" << endl;
}
//생성자 위임을 사용하는 깊은 복사 생성자
Deep::Deep(const Deep &source){
:Deep{*source.data}
{
cout << "Copy consturctor, deep" << endl;
}
깊은 복사의 경우 데이터를 복사하므로 이중 해제의 문제 해결
복사 생성자 잘 사용하는 법
포인터 타입의 멤버 변수가 존재할 때는(깊은)복사 생성자 직접 구현
→ 새로운 heap 공간을 할당하여 값을 복사해 두어야 한다는 것을 명심
STL 또는 smart pointer 사용
→ STL은 내부적으로 안전한 복사 생성자가 구현되어 있음
→ Smart pointer는 복사를 허용하지 않거나(unique)참조되어 있는 동안은 해제되지 않음(shared)
Object in Memory
객체를 메모리에 생성하면 멤버 변수의 값이 스택이나 힙에 저장되는것
멤버 함수는 코드 공간에 존재하며 접근 제한에 따라 일반 함수처럼 호출될 뿐이다.
class Point
{
private:
int x, y;
public :
Point(int x, int y)
:x{x}, y{y}
{}
};
int main()
{
Point p{2, 3};
}
댓글