관리 메뉴

기억을 위한 기록들

[Fundamental C++] 6. 클래스 (4) - 타입변환 연산자, 상속 본문

C & CPP/Fundamental C++

[Fundamental C++] 6. 클래스 (4) - 타입변환 연산자, 상속

에드윈H 2021. 5. 4. 11:37

타입 변환 연산자

클래스에서 타입 변환 연사자를 사용하려면 아래와 같다.

operator TYPE()
{
	//변환코드
}

간단한 예를 들어서 어떤 문자열을 입력받으면 자동으로 해당 문자열의 길이를 출력해보도록 하자.

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


class MyClass 
{
public:
	MyClass(string& str)
	{
		mLength = str.length();
	}	

	operator int() //타입변환 연산자
	{
		return mLength; //문자열길이 반환
	}

	int mLength;
};


int main()
{
	string helloStr = "Hello";
	MyClass a(helloStr);

	int length = a; //타입변환 연산자

	cout << length << endl;

	return 0;
};

실행해보면 문자열의 길이가 출력되는 것을 확인할 수 있다.

 

 

 

상속 클래스의 메모리 구조

기존 클래스 메모리 구조와 크게 다르지 않다.

using namespace std;
class Parent
{
	int mPInt;
};

class MyClass :public Parent
{

	int mInt;
    static int sInt;
};

int main()
{
	cout << sizeof(Parent) << endl;
	cout << sizeof(MyClass) << endl;

	return 0;
};

Parent 클래스 : mPInt 4바이트
Parent 클래스 상속받는 MyClass 클래스 : mPInt 4바이트, mInt 8바이트

 

마찬가지로 static int도 MyClass 클래스 구조에 포함되지 않는다.

 

그리고 같은 이름으로 선언된 변수가 있다면

class Parent
{
	int mInt;
};

class MyClass :public Parent
{
	int mInt;	
};

 

	MyClass a;

	a.mInt = 5;   //MyClass mInt 접근
	a.Parent::mInt = 5; // Parent mInt 접근

보통 이름탐색 규칙이라고 부르고, 기본적으로 클래스 상속에 있어서 자식 클래스의 범위부터 탐색을 시작하는 셈이다.

자식 클래스에서 탐색에 실패하면 부모 클래스의 탐색으로 확장하여 탐색하게 된다.

 

정적 멤버 변수

class MyClass 
{
public:
	static int mInt;	
};


int main()
{
	MyClass a;
	MyClass b;

	a.mInt = 5;
	b.mInt = 10;
        MyClass::mInt = 15;
	return 0;
};

정적 멤버변수는 클래스의 객체와 상관없이 무조건 1개만 정의된다. 위의 코드의 mInt는 5,10,15로 접근하게 되어 마지막인 15가 된다. 하지만 컴파일은 성공하는데 링크 에러가 발생하게 된다. 그 이유는 컴파일할 땐 문제가 없지만, 실제로 정의가 되지 않기에 mInt는 메모리에 할당될 수 없고, 그로 인해 메모리 주소로 변환하는 과정에서 에러가 발생한다.

 

즉, 정적 멤버 변수는 선언뿐 아니라, 정의도 반드시 필요하다. 정의는 어떻게 해야 할까?

//Test.h
class MyClass 
{
public:
	static int mInt;	
};


//Test.cpp
int MyClass::mInt;

클래스 외부에 이와 같이 범위 연산자를 이용하여 정적 멤버 변수를 정의한다.

클래스 외부라는 것은 클래스 정의 밖을 의미하고 클래스 정의가 속한 헤더 파일이 아닌 클래스 멤버 함수들이 정의되는 cpp파일을 의미한다.

 

 

 

접근 지정자와 정적 멤버 변수

class AClass
{
private:
	AClass() {}
};

class BClass 
{
private:
	BClass() {}
public:
	static AClass p;
	static BClass c;
};

AClass BClass::p; //error!
BClass BClass::c;

 

접근 지정자에 따라 객체 생성에 문제가 발생할 수도 있다. 정적 멤버 변수의 생성자가 호출되는 환경은 바로 정적 멤버가 선언된 클래스라는 것이다.

 

즉, 정적 멤버가 선언된 클래스가 해당 정적 멤버의 생성자를 호출할 수 있도록 접근 가능한지가 중요한 문제가 된다.

위의 코드를 보면 AClass 생성자인 BClass 멤버 변수 p는 컴파일 에러가 발생한다. 이유는 AClass의 생성자가 private이기 때문이다.

하지만, BClass도 마찬가지로 private이긴 하지만, 호출되는 환경이 BClass이기에 문제가 되지 않는다. 이런 원리로 싱글톤 객체를 생성할 때도 이용된다.

 

 

정적 멤버 변수 상속

using namespace std;
class Parent
{
public:
	static int mPInt;
};

class Child : public Parent
{

};

int Child::mPInt;

int main()
{
	Child::mPInt = 5;
	Parent::mPInt = 1;
	return 0;
};

위와 같은 코드를 실하면 최종 mPInt은 마지막인 1이 된다.

 

using namespace std;
class Parent
{
public:
	static int mInt;
};

class Child : public Parent
{
	static int mInt;
};

int Child::mInt;

int main()
{
	Parent::mInt = 1;
	Child::mInt = 2;
	Child::Parent::mInt = 3;
	return 0;
};

이렇게 되면 이름은 같더라도 각각의 정수형 변수 mInt가 존재하게 되고, Parent의 mInt 값은 3, Child의 mInt는 2가 된다.

 

 

클래스 함수의 상속

 

1. 생성자는 상속되지 않는다.

 

2. 대입 연산자도 상속되지 않는다.

부모 클래스에 명시적 대입 연산자가 있고, 자식 클래스에는 없어도, 자식 클래스에서 대입 연산자가 작동할수는 있다.  헷갈릴수 있겠지만 자식클래스의 대입연산자는 명시적으로 선언한 대입연산자가 아니라, 암시적인 대입 연산자이다.

 

3. 타입 변환 연산자는 상속된다.(Good)

 

4. 재정의된 일반 연산자도 상속된다(Good)