본문 바로가기
C++ STL, 알고리즘

C++ 생성자

by wanna_dev 2024. 10. 2.

생성자

class SpreadsheetCell
{
	public:
		SpreadsheetCell(double initialValue);
		SpreadsheetCell(string initialValue);
}
SpreadsheetCell::SpreadsheetCell(double initialValue){
	setValue(initialValue);
}

SpreadsheetCell::SpreadsheetCell(string initialValue){
	setString(initialValue);
}

스택생성자

SpreadsheetCell myCell(5), anotherCell(4);
cout<<"cell 1: "<<myCell.getValue() << endl;
cout<<"cell 2: "<<anotherCell.getValue() << endl;

힙 객체와 생성자

SpreadsheetCell *myCellp = new SpreadsheetCell(5);
SpreadsheetCell *anotherCellp = nullptr;
anotherCellp = new SpreadsheetCell(4);
// --- 셀 객체를 이용 ---
delete myCellp;
myCellp = nullptr;
delete anotherCellp;
anotherCellp = nullptr;

복수생성자 → overloading

SpreadsheetCell aThirdCell("test");
SpreadsheetCell aFourthCell(4.4);
SpreadsheetCell* aThirdCellp = new SpreadsheetCell("4.4");

명시적인 디폴트 생성자

class MyClass{
public : 
	MyClass() = **default;**
	MyClass(int i);
}

명시적으로 삭제된 생성자

class MyClass{
public:
	MyClass() = delete;
}

생성자 초기화 리스트(constructor initializer) : 생성자 바디에서 코드를 통해 초기화 하는 것보다 훨씬 효율적임.

SpreadsheetCell::SpreadsheetCell():mValue(0), mString(""){
}

constructor initializer를 통해서만 초기화할 수 있는 경우

class에서 선언된 멤버변수의 순서에 따라 리스트가 초기화 된다.

복제생성자 : 객체의 복제본을 만들어야할 때 사용

프로그래머가 명시적으로 복제 생성자를 만들지 않으면 컴파일러가 자동으로 만들어준다.

컴파일러가 만들어주는 복제 생성자는 기본타입 멤버는 그대로 복제하고 객체타입 멤버는 그 객체의 복제 생성자를 호출해준다.

class SpreadsheetCell{
public:
	SpreadsheetCell(const SpreadsheetCell& src);
	
}

복제 생성자에서는 파라미터로 받는 원본 객체에 대해 상수 참조를 하므로 const와 &가 붙어있다.

다른 객체와 똑같은 객체를 생성할때 복제생성자를 사용한다.

생성자 안에서 원본 객체에 있는 데이터 멤버를 모두 복사한다.

SpreadsheetCell::SpreadsheetCell(const SpreadsheetCell& src) : mValue(src.mValue){
} 

복제생성자가 호출되는 경우

C++에서 하무에 인수를 전달할 때 기본적으로 값으로 전달된다(값 전달방식이 적용된다.)

다시말해 함수나 메서드는 값이나 객체의 복사본을 받는다. 따라서 함수나 메서드에 객체를 전달하면 컴파일러는 그 객체의 복제 생성자를 호출하는 방식으로 초기화 한다.

예를들어, 다음과 같이 string 매개변수를 값으로 받는 printString() 함수가 있다고 하자.

void printString(string inString){
	cout<<inString<<endl;
}

string은 기본타입이 아니라 클래스이다. 그래서 코드에서 printString()에 string 매개변수를 전달해서 호출하면 string 매개변수인 inString은 이 클래스의 복제생성자를 호출하는 방식으로 초기화 된다. 이 복제 생성자의 인수가 바로 printString()에 전달한 string이다.

명시적인 복제 생성자 호출

SpreadsheetCell myCell2(4);
SpreadsheetCell myCell3(myCell2); //myCell3는 myCell2 와 같은 값을 가진다.

initializer_list 생성자

class PointSequence
{
public:
	PointSequence(initializer_list<double> args){
		if(args.size()%2 != 0){
			throw invalid_argument("initializer_list should contain even number of elements");
		}
		for(auto iter = args.begin(); iter != args.end(); ++iter){
			mVecPoints.push_back(*iter);
		}
		//for(const auto& value : args){
		// mVecPoints.push_back(value);
		//}
	}
	void dumpPoints() const{
		for(auto citer = mVecPoints.cbegin(); citer != mVecPoints.cend(); citer +=2){
			cout<<"(" << *citer << ", " << *(citer+1) << ")" << endl;
		}
	}
protected:
	vector<double> mVecPoints;
}

생성자 위임

생성자 안에서 같은 클래스의 다른 생성자를 호출하는 것

생성자 바디에서 다른 생성자를 부를 수는 없고 다음처럼 생성자 초기화 리스트를 이용해야한다.

SpreadsheetCell::SpreadsheetCell(const string& initialValue)
	: SpreadsheetCell(stringToDouble(initialValue)){}

생성자 위임할 때는 재귀적으로 꼬리를 무는 상황을 피해야 한다.

객체가 소멸할 때는 두가지 작업이 발생

  1. 객체의 소멸자가 호출되고 난 후, 할당받은 메모리가 반환

소멸자를 정의하지 않으면, 컴파일러가 자동으로 생성해줌

자동으로 생성된 소멸자는 클래스 내 멤버들을 돌아가며 재귀적으로 소멸자를 호출하여 객체가 삭제될 수 있도록 한다.

스택에 생성된 객체는 유효범위에서 벗어날 때 자동으로 삭제된다.

스택에 생성된 객체는 생성 순서의 역순으로 삭제된다.

SpreadsheetCell myCell2(4);
SpreadsheetCell anotherCell2(5); // myCell2은 anotherCell2보다 먼저 생성된다.
//myCell2은 anotherCell2보다 뒤에 소멸한다.

이러한 생성/소멸 순서는 객체의 데이터 멤버에도 동일하게 적용된다.

생성자에서 멤버 초기화를 할때 복제 생성자가 사용되는지 대입 연산자가 사용되는지 잘 생각해야한다.

데이터 멤버 중 객체가 있으면, 컴파일러가 자동으로 생성하는 복제 생성자에서는 멤버들의 복제 생성자를 재귀적으로 호출한다.

생성자

class SpreadsheetCell
{
	public:
		SpreadsheetCell(double initialValue);
		SpreadsheetCell(string initialValue);
}
SpreadsheetCell::SpreadsheetCell(double initialValue){
	setValue(initialValue);
}

SpreadsheetCell::SpreadsheetCell(string initialValue){
	setString(initialValue);
}

스택생성자

SpreadsheetCell myCell(5), anotherCell(4);
cout<<"cell 1: "<<myCell.getValue() << endl;
cout<<"cell 2: "<<anotherCell.getValue() << endl;

힙 객체와 생성자

SpreadsheetCell *myCellp = new SpreadsheetCell(5);
SpreadsheetCell *anotherCellp = nullptr;
anotherCellp = new SpreadsheetCell(4);
// --- 셀 객체를 이용 ---
delete myCellp;
myCellp = nullptr;
delete anotherCellp;
anotherCellp = nullptr;

복수생성자 → overloading

SpreadsheetCell aThirdCell("test");
SpreadsheetCell aFourthCell(4.4);
SpreadsheetCell* aThirdCellp = new SpreadsheetCell("4.4");

명시적인 디폴트 생성자

class MyClass{
public : 
	MyClass() = **default;**
	MyClass(int i);
}

명시적으로 삭제된 생성자

class MyClass{
public:
	MyClass() = delete;
}

생성자 초기화 리스트(constructor initializer) : 생성자 바디에서 코드를 통해 초기화 하는 것보다 훨씬 효율적임.

SpreadsheetCell::SpreadsheetCell():mValue(0), mString(""){
}

constructor initializer를 통해서만 초기화할 수 있는 경우

class에서 선언된 멤버변수의 순서에 따라 리스트가 초기화 된다.

복제생성자 : 객체의 복제본을 만들어야할 때 사용

프로그래머가 명시적으로 복제 생성자를 만들지 않으면 컴파일러가 자동으로 만들어준다.

컴파일러가 만들어주는 복제 생성자는 기본타입 멤버는 그대로 복제하고 객체타입 멤버는 그 객체의 복제 생성자를 호출해준다.

class SpreadsheetCell{
public:
	SpreadsheetCell(const SpreadsheetCell& src);
	
}

복제 생성자에서는 파라미터로 받는 원본 객체에 대해 상수 참조를 하므로 const와 &가 붙어있다.

다른 객체와 똑같은 객체를 생성할때 복제생성자를 사용한다.

생성자 안에서 원본 객체에 있는 데이터 멤버를 모두 복사한다.

SpreadsheetCell::SpreadsheetCell(const SpreadsheetCell& src) : mValue(src.mValue){
} 

복제생성자가 호출되는 경우

C++에서 하무에 인수를 전달할 때 기본적으로 값으로 전달된다(값 전달방식이 적용된다.)

다시말해 함수나 메서드는 값이나 객체의 복사본을 받는다. 따라서 함수나 메서드에 객체를 전달하면 컴파일러는 그 객체의 복제 생성자를 호출하는 방식으로 초기화 한다.

예를들어, 다음과 같이 string 매개변수를 값으로 받는 printString() 함수가 있다고 하자.

void printString(string inString){
	cout<<inString<<endl;
}

string은 기본타입이 아니라 클래스이다. 그래서 코드에서 printString()에 string 매개변수를 전달해서 호출하면 string 매개변수인 inString은 이 클래스의 복제생성자를 호출하는 방식으로 초기화 된다. 이 복제 생성자의 인수가 바로 printString()에 전달한 string이다.

명시적인 복제 생성자 호출

SpreadsheetCell myCell2(4);
SpreadsheetCell myCell3(myCell2); //myCell3는 myCell2 와 같은 값을 가진다.

initializer_list 생성자

class PointSequence
{
public:
	PointSequence(initializer_list<double> args){
		if(args.size()%2 != 0){
			throw invalid_argument("initializer_list should contain even number of elements");
		}
		for(auto iter = args.begin(); iter != args.end(); ++iter){
			mVecPoints.push_back(*iter);
		}
		//for(const auto& value : args){
		// mVecPoints.push_back(value);
		//}
	}
	void dumpPoints() const{
		for(auto citer = mVecPoints.cbegin(); citer != mVecPoints.cend(); citer +=2){
			cout<<"(" << *citer << ", " << *(citer+1) << ")" << endl;
		}
	}
protected:
	vector<double> mVecPoints;
}

생성자 위임

생성자 안에서 같은 클래스의 다른 생성자를 호출하는 것

생성자 바디에서 다른 생성자를 부를 수는 없고 다음처럼 생성자 초기화 리스트를 이용해야한다.

SpreadsheetCell::SpreadsheetCell(const string& initialValue)
	: SpreadsheetCell(stringToDouble(initialValue)){}

생성자 위임할 때는 재귀적으로 꼬리를 무는 상황을 피해야 한다.

객체가 소멸할 때는 두가지 작업이 발생

  1. 객체의 소멸자가 호출되고 난 후, 할당받은 메모리가 반환

소멸자를 정의하지 않으면, 컴파일러가 자동으로 생성해줌

자동으로 생성된 소멸자는 클래스 내 멤버들을 돌아가며 재귀적으로 소멸자를 호출하여 객체가 삭제될 수 있도록 한다.

스택에 생성된 객체는 유효범위에서 벗어날 때 자동으로 삭제된다.

스택에 생성된 객체는 생성 순서의 역순으로 삭제된다.

SpreadsheetCell myCell2(4);
SpreadsheetCell anotherCell2(5); // myCell2은 anotherCell2보다 먼저 생성된다.
//myCell2은 anotherCell2보다 뒤에 소멸한다.

이러한 생성/소멸 순서는 객체의 데이터 멤버에도 동일하게 적용된다.

생성자에서 멤버 초기화를 할때 복제 생성자가 사용되는지 대입 연산자가 사용되는지 잘 생각해야한다.

데이터 멤버 중 객체가 있으면, 컴파일러가 자동으로 생성하는 복제 생성자에서는 멤버들의 복제 생성자를 재귀적으로 호출한다.

 

출처 : 전문가를 위한 C++

'C++ STL, 알고리즘' 카테고리의 다른 글

C++ 상속  (3) 2024.10.02
const  (0) 2024.10.02
OOP  (0) 2024.10.02
C++ 알쓸신잡  (0) 2024.10.02
c++ static  (0) 2024.10.02