본문 바로가기
CODING/C++ STUDY

C++ 독학 12일차 - 가상함수 구현원리, 다중 상속, istream, 형식플래그, streambuf, 조작자

by pharmerci 2021. 8. 17.
728x90

가상함수의 구현원리

 

 

가상함수를 배우다보면,,, 모든 함수를 virtual 처리하면 안되나? 하는 생각이 든다.

 

virtual로 함수를 처리하면 지들이 알아서 눈치껏 함수들을 처리해주니까.

 

실제로 자바는 모든 함수들이 virtual 함수로 선언된다고 한다.

 

 

 

그런데 c++는 사람들에게 virtual 키워드로 직접 선언하게 하는 이유는 오버헤드가 존재하기 때문이다.

 

 

 

일반 함수를 호출하는 것보다 가상함수를 호출하는데 시간이 더 오래 걸린다.

 

 

 

virtual 클래스가 어떻게 구현되는지 생각해보자.

 

 

이런 클래스를 가정해보자.

 

컴파일러는 가상함수가 하나라도 있으면 가상함수 테이블을 만든다.

 

 

이렇게 구성이 된다.

그러면 가상함수를 실행하려 할 때에는 먼저 해당하는 클래스를 가서 함수를 탐색하는데,

가상함수면 직접 함수를 실행하는게 아니라 가상함수 테이블에서 함수를 실행해야겠다고 판단한다. 

 

일반함수면 바로 함수를 실행하는데 가상함수는 한단계를 더 거치기 때문에 시간이 오래 걸린다.

 

차이는 미미하지만 최적화가 중요한 분야에서는 감안할 필요가 있다. 그래서 C++에서는 디폴트로 가상함수가 되도록 설정하지 않는다.

 

 

 

 


순수 가상 함수와 추상 클래스

 

위에서부터 읽으면 편하게 이해할 수 있다.

 

 

 

 

 

실행은 이렇게 된다!

 

 

 

 


다중상속

 

C++에서는 한 클래스가 다른 여러개의 클래스들을 상속 받는 것을 허용한다.

 

이것을 다중 상속이라 한다.

 

class A { public: int a; };

 

class B { public: int b; };

 

class C : public A, public B { public: int c; };

 

이것이 가능하다는 것.

 

생성자가 호출되는 순서는 오직 상속하는 순서에만 좌우된다.

 

다중 상속을 할 때 상속하는 여러개의 클래스들은 똑같은 변수를 이용하면 안된다. 그러면 어디의 변수인지 구분할 수 없어서 컴파일 오류가 발생한다. 함수 이름도 마찬가지로 겹치게 쓰면 안된다.

 

다중 상속 시 또 주의해야 할 점은 다이아몬드 상속 형태일 때이다. 

 

 

 

대충 이런식으로 상속하고 상속받는 형태인데 두개의 클래스가 공통의 베이스를 포함하는 형태를 다이아몬드 상속이라고 한다.

 

예를 들어서 human에 name 변수가 있었다고 하면, handsomehuman이랑 smarthuman에 name변수가 겹치게 들어간다.

그러면 me는 어차피 name변수가 중복되는 문제가 발생한다. name만 중복되는게 아니라 human에 있는 모든 변수가 중복된다.

 

 

 

 


istream 클래스

 

istream은 실제로 입력을 수행하는 클래스이다.

 

std::cin>>a;

우리가 출력할 때 쓰는 >> 연산자가 이 클래스에 있는 연산자이다.

cin은 istream의 객체 중 하나이다.

 

우리는 cin을 어떤 타입에 대해서도 사용할 수 있는데 그 이유는 >> 연산자가

모든 기본 타입에 대해서는 정의되어있기 때문이다. 정수형 문자형 등등 모두를 수용할 수 있게 만들어놨다.

 

>>의 특징은 모든 공백문자를 무시한다는 점이다. cin을 통해서 문장을 입력받으면 첫 단어만 입력받고 나머지는 못읽는다.

 

ios클래스에서 스트림의 상태를 관리한다. 스트림의 상태를 관리하는 플래그는 4개가 있다. 4개의 플래그들이 어떤 상태인지에 대해 정보를 보관한다. 플래그는 goodbit, badbit, eofbit, failbit 4종류가 있다.

 

goodbit : 스트림에 입출력이 가능할때

badbit : 스트림에 복구 불가능한 오류 발생시

failbit : 스트림에 복구 가능한 오류 발생시

eofbit : 입력 작업 시 EOF 도달 시

 

각각의 경우에 켜진다.

 

 

주석을 보면 코드가 이해될 것이다.

 

 

 

 

 

실행은 다음과 같이 된다.

 

 

 

 


형식 플래그와 조작자

 

setf 함수는 스트림의 설정을 바꿔준다.

 

setf 함수의 버즌은 두개가 있다. 한개는 인자를 1개만 받는 것이고, 다른 하나는 인자를 2개 받는 것이다.

인자를 1개 받는 함수는 인자로 준 형식 플래그를 적용하는 것이고, 2개 받는 함수는 두번째 인자의 내용을 초기화하고 첫번째 인자를 적용하는 것이다. 

 

std::cin.setf(ios_base::hex, ios_base::basefield);

 

이런 코드가 있다면 basefield의 값을 초기화하고 hex 플래그를 적용시킨 것이다.

 

std::cin >> hex >> t;

 

이런식으로도 16진수를 받을 수 있다. hex가 cin에서 수를 받는 방식을 바꿨기 때문이다. hex와 같이 스트림을 조작하여 입출력 방식을 바꾸는 함수를 조작자라고 부른다. 첫번째 방식은 형식플래그의 hex이고 두번째 방식에서의 hex는 함수인 것이다.!

 

첫번째 방식보다 두번째 방식이 훨씬 쉬워보인다. setf를 사용하지 않더라도 간단하게 조작자를 사용하면 쉽게 입력형식을 바꿀 수 있다.

true false를 1과 0으로 처리하는 대신에 문자열을 그대로 입력받는 boolalpha

출력 형식을 왼쪽정렬 혹은 오른쪽정렬로 만드는 left, right 조작자들도 있다.

std::endl;도 우리가 많이 쓰던것인데 한줄개행문자 출력 말고도 버퍼를 모두 내보내는 역할도 한다. 버퍼를 모두 내보낸다는 것은 버퍼에 쌓여있는 문자들을 한번에 출력한다는 것이다.

 

 

 

 


스트림버퍼

 

모든 입출력 객체들은 대응하는 스트림객체를 갖는다. C++에서는 대응되는 스트림버퍼 클래스가 있는데 이 이름이 streambuf 클래스이다. 스트림은 문자의 순차적인 나열이다.

 

std::stringstream을 통해 평범한 문자열을 스트림인것처럼 이용할 수 있다. streambuf 클래스는 스트림에 대한 기본적인 제어를 담당한다. streambuf 클래스에서는 스트림의 상태를 나타내기 위해서 세개의 포인터를 정의한다.

1. 버퍼 시작부분을 가리키는 시작포인터

2. 다음으로 읽을 문자를 가리키는 포인터

3. 버퍼의 끝부분을 가리키는 포인터

 

streambuf 클래스에서는 입력버퍼를 get area, 출력버퍼를 put area라고 부르는데 각 포인터를 g, p를 붙여서 표현한다.

 

 

streambuf 클래스가 사용되는 간단한 예시 코드이다.

주석을 읽어보면 무슨 뜻인지 이해할 수 있을 것이다.

 

 

 

 

 

결과 창!

 

 

728x90