관리 메뉴

기억을 위한 기록들

[Fundamental C++] 6. 클래스 (3) - 등가연산자, 초기화 리스트 본문

C & CPP/Fundamental C++

[Fundamental C++] 6. 클래스 (3) - 등가연산자, 초기화 리스트

에드윈H 2021. 5. 3. 20:11

등가 연산자

좌변 우변이 같은지, 다른지(==, !=)를 평가하는 연산자이다. 등가라는 개념은 대입이라는 개념과 함께한다.

int a = 3;
int b = 3;


if(a == b) //ok
{
  std::cout<< " 같다 " <<std::endl;
}

int c = 3;
double d = 3.0;


if(c == d)  //다르다?
{
  std::cout<< " 같다 " <<std::endl; //
}

a와 b는 "같다"가 출려이 되고, 마찬가지로 c와 d도 "같다"가 출력이 된다.

무엇을 기준으로 판별할까??컴파일러는 메모리비트 상태를 비교하는게 아니라, 값 자체를 비교한다.

a와 b는 메모리의 비트상태는 같지만, c와 d는 달라도 값이 같으므로 같다가 출력 되는 것이다.

일반 타입은 값으로 비교를하고 클래스는 어떻게 비교할까?

 

 

using namespace std;

class MyClass
{
public:
	MyClass(int value)
	{
		mVal = value;
	}

	int mVal;
};





int main()
{
	MyClass a=3;
	MyClass b=3;

	if (a == b) //에러!
	{
		cout << "같다" << endl; 
	}
	return 0;
};




일반 정수 타입처럼 값을 초기화하고 비교해보려 했으나, 컴파일 에러가 발생한다..

 

비교자체를 하기전에 컴파일 에러는 등호(==)연산자가 정의되지 않았다는 에러이다. 컴파일러는 클래스자체에 등호 연산자로 뭘 비교해야할지 확실히 알려달라고 하는것 같다.(클래스의 어떤크기를 비교해야할지, 클래스의 어떤 멤버변수의 값을 비교해야하는지 등..) 그래서 알려주도록 하자

class MyClass
{
public:
	MyClass(int value)
	{
		mVal = value;
	}

	bool operator==(const MyClass& ref) //Val값이 같은지 다른지 
	{
		if (mVal == ref.mVal)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	int mVal;
};


int main()
{
	MyClass a=3;
	MyClass b=3;

	if (a == b)
	{
		cout << "같다" << endl;
	}
	return 0;
};

클래스에 등호(==)연산자를 선언해주었더니 문제없이 작동한다.

초기화 리스트

초기화리스트는 효율을 높이기 위해서 사용하는게 아니다. 필요에 따르면 쓸수밖에 없는 경우들이 있다.

#include <iostream>
using namespace std;

class Parent
{
public:
	Parent()
	{
		mPVal = 0;
	}

	Parent(int value)
	{
		mPVal = value;
	}
	int mPVal;
};


class MyClass : public Parent
{
public:
	MyClass(int value)
	{		
	}

	int mVal;
};


int main()
{
	MyClass a(3);

	cout <<" 부모의 변수 값: "<< a.mPVal << endl;
	cout << "자식의 변수 값: " << a.mVal << endl;
	return 0;
};




결과는 이러하다. 이런 이유는 MyClass의 생성자가 호출되면서 부모 Parent 클래스가 호출이 되긴하는데, mPval변수의 값은0 으로만 초기화되고, mVal의 값은 초기화되지 않기에 쓰레기값이 들어가게 된다.

 

제대로 값을 넣어주고 싶다면, MyClass생성자 초기화리스트를 수정해주도록 하자.

 

//..Parent 클래스는 그대로

class MyClass : public Parent
{
public:
	MyClass(int value)
		: mVal(value)  //추가
		, Parent(value) //추가
	{		
	}

	int mVal;
};


int main()
{
	MyClass a(3);

	cout <<" 부모의 변수 값: "<< a.mPVal << endl;
	cout << "자식의 변수 값: " << a.mVal << endl;
	return 0;
};

위와 같이 변경해주게 되면

 

0과 쓰레기 값이 아닌 위와 같이 출력이 되는것을 확인할 수 있다.

 

생성자를 호출할때 상속받는 클래스가 있고, 초기화리스트가 없다면 암시적으로 부모클래스의 생성자를 호출될수 밖에 없기에, 경우에 따라 있어도 그만 없어도 그만이 아니라 초기화 리스트에 절대로 없어서는 안된다. 

 

초기화 리스트에서의 주의할점

이전 코드 그대로 가져와 MyClass클래스에서 Parent 클래스의 멤버변수를 초기화리스트에서 초기화 해줘도될까? 안된다.

 

class MyClass : public Parent
{
public:
	MyClass(int value)
		: mVal(value)
		, mPVal (value) //부모 소유의 변수 
		
	{		
	
	}

	int mVal;
};

위와 같이 컴파일에러가 발생한다. MyClass는 Parent를 상속받고 있는데 왜 안되는것일까? 그것은 생성자 영역(scope)가 아닌 선처리 영역(초기화리스트 있는곳)은 멤버라는 자기 자신의 클래스에서 처음으로 선언된 멤버를 의미하고, 물려받고 있는 멤버를 의미하고 있지 않다. 

 

한마디로 초기화리스트는 선처리 영역을 제어하는 목적으로 존재한다. 따라서 선처리 영역에서 이미 처음 선언된 멤버 대해서만 초기화를 하기에 초기화리스트에서도 해당 멤버에 대해서만 제어 할수 있다는 것이다.

class MyClass : public Parent
{
public:
	MyClass(int value)
		: mVal(value)
		, Parent(value)
		
	{		
		mPVal = 100; //?
	}

	int mVal;
};


int main()
{
	MyClass a(3);

	cout <<"부모의 변수 값: "<< a.mPVal << endl;
	cout << "자식의 변수 값: " << a.mVal << endl;
	return 0;
};

 

이렇게 실행하게 된다면 컴파일에러는 발생하지 않지만,  Parent클래스에서의 mPval 변수에 3이라는 값을 넣으려고 했지만, 100이 들어가게 되어 헷갈리게 되는 코드가 된다.

 

즉, 각자의 클래스에서 처음 선언된 멤버에 대한 초기화를 각자의 초기화리스트에서 해주는것이 낫다고 볼수있다.

 

그외에도 참조타입, 상수타입도 초기화가 초기화리스트를 통해서만 가능하다. 컴파일러는 생성자의 선처리 영역에서만 객체가 최초로 초기화 될수 있다고 생각한다. 생성자 영역(scope)에서는 불가능하다.

class MyClass 
{
public:
	MyClass(int value)
		: mConstVal(value) //ok 
		, mRefVal(value) //ok
		
	{			
		mConstVal = value;// error
		mRefVal = value;// error
	}

	const int mConstVal;
	int& mRefVal;
};


int main()
{
	MyClass a(3);
	return 0;
};