삽질의 현장/- C++

#030_시(c)시(c)해서 C++?!_C++의 모든 것! 최종 정리 (2)

shovelman 2015. 7. 28. 23:54


안녕하세요 삽잡이입니다...

지난시간에 이어서 C++ 에 대한 총 정리를 진행해보려고 합니다...

물론 '백문이 불여일타' 라는 말이 있듯이(?) 하하...

열심히 코드를 쳐보며 접근해야하는 것이 맞으나,


가끔은 머리도 순환할겸... 개념도 정리할겸... 

이렇게 정리를 통해 복습하는 것 또한 나쁘지 않다고 생각합니다...


이전 시간에도 언급했듯이,

이 글은 이미 C++에 대한 경험이 있으시며, 

다시한번 머리로 정리해볼 필요가 있으신 분들이 읽으시기에 보다 적합한 글이 될 것 같습니다. 


물론 제가 글을 워낙 막쓰다보니... 이해가 안가시는 부분들이 많을 수 있겠지만....

사전적 지식을 가지고 계신 분들이라면,

무리없이 이해하시고 넘어가실 것이라는 믿음이 생기는 군요...


말이 길어졌군요... C++ 마지막(?) 총 정리 2부 시작합니다!



13.

다음으로, OOP의 삼대장중 두번째 대장 일반화에 대해 설명해보겠습니다.


일반화에 대한 설명에 들어가기 전에 용어에 대한 구분을 해보도록 하겠습니다.

우선 일반화 관계라는 용어는 UML 언어에서 나오는 즉, 설계... 개발 방법론에서 사용하는 언어입니다.

파생 문법이라는 용어는 프로그래밍 언어에서 말하는 것입니다.

상속이라고 하는 것은 OOP 즉, 관념적인 프로그래밍 언어에서 말하게 되는 것입니다.


이처럼 같은 것을 표현하기 위한 것인데 

바라보는 분야에 따라 다르게 표현한다는 것을 말씀드리고자 주절 거려봤습니다...


아무튼...

일반화는 'is - a' 의 논리 관계가 성립해야합니다.


예를 들어보도록 하겠습니다.

'삽잡이는 사람이다.', '컴퓨터는 기계다.', '개발자는 직업이다.' 등 과 같이 모두 한쪽으로 

일반화가 가능해야합니다.


일반화를 왜 언급하냐에 대해 여쭈어 보신다면 

바로, 부모와 자식 클래스 즉, 기반 클래스와 파생클래스를 설명할 수 있게 되기 때문입니다.

실제로 공통적인 부분들을 묶고 부모 클래스로 구현해 놓는 다면

추후 부모 클래스로 부터 파생된 여러 클래스들을 생성하고, 관리할 때 매우 효율적이게 됩니다.


부모(기반) 클래스로 부터 자식(파생) 클래스도 일반적인 객체를 생성하며 생성자가 호출 되듯이

마찬가자기로 생성자를 호출하며 초기화를 진행합니다.

다만, 차이점이 있다면...

파생 클래스의 생성자가 호출 되기 이전에 반드시 부모 클래스의 생성자가 호출 된다는 것입니다.


이전에 상수에 대한 초기화를 하기 위해 이니셜라이저(:)를 사용한다고 했습니다.

이 이니셜라이저(:)가 한번 더 사용될 수 있는데 이때가 바로 부모 생성자를 초기화할 때입니다.

어떻게 부모 (기반) 클래스를 초기화 해줄지에 대한 결정을 이니셜라이즈를 통해 하는 것이지요.


1
2
3
4
5
 
Sapzape(string name, int age, int power) : Human(name, age, power)
{
}
 
cs


이와 같이 말입니다...


파생된 객체로 부터의 생성과 소멸 과정을 살펴보자면, 

부모 생성자 -> 자식 생성자 -> 자식 소멸자 -> 부모 소멸자 와 같은 과정이 진행 되게 됩니다.


13.5

일반화에 대한 썰을 풀다보니 다른 관계들에 대해서도 언급을 하지 않을 수 없을 것 같습니다...


일반화 관계 이외에도, 집합, 구성, 연관, 직접 연관, 의존 관계, 실체와 관계등이 있습니다.

자세한 내용은 UML 에 대해 구글링이나 기타 검색을 통해 접근하시면

쉽게 알아보실 수 있을 것입니다.


이 중에서도 연관 관계와 같은 경우에는 반드시 피해야하는 관계입니다.

'A는 B에 명령을 내릴 수 있다. 또한, B도 A에 명령을 내릴 수 있다.' 

이와 같은 관계를 연관 관계라고 표현할 수 있습니다.


왜 피해야한다고 했을까요?

서로가 서로를 인지하기에 수평적인 관계에 놓여 있게 됨으로

많은 오류들을 발생시킬 위험성이 아주 농후한 관계입니다.


쉽게 생각해보자면, 군대를 예시로 들어보겠습니다.

각각 계급과 호봉에 맞춰 움직이는 계급 사회인 군대에서는 수직적으로 명령을 하달하기에

(뒷 말이 나올지 몰라도) 업무 효율이 높습니다.

물론 동의하지 않을 수 있지만, 일병들만 모여있을 때 업무가 잘 될까요...

아니면 병장 몇몇, 상병 몇몇, 그리고 일병들이 보여있을 때 업무가 잘 될까요...

한번 생각해보시길 바랍니다....


즉, 업무 효울과 보다 높은 버그 발생율을 줄이기 위해서 전산에서는 수직 적인 관계가 되도록

반드시 신경 써야합니다.


14.

부모 클래스로부터 파생된 자식 클래스에서는 

부모 클래스의 멤버 변수 및 메소드를 사용할 수 있습니다. (단, 접근 지정자에 따라 사용 유무가 나뉩니다.)

이 때 자식 클래스에서 동일한 메소드명이지만 기능이 달라질 수 있습니다.

이를 무효화라고 하며, 오버 라이딩이라고도 합니다.


오버로딩과 오버라이딩에 대해서 헷갈려 하실 수 있기에 정리를 해보겠습니다.


파생된 클래스에서 기반 클래스와 같은 메서드를 정의하는 것을 

바로 재정의 즉, 오버라이딩이라고 한다고 했습니다.

정의를 새롭게 하는 것입니다. 기존에 정의된 내용은 무효화가 되는 것이지요...


같은 이름의 여러개의 메소드를 정의하는 것은 중복 정의라고 하며 오버로딩이라고 부르지요...

같은 스코프 내에서 시그니처만 다르게 한다면 중복 정의입니다.

재정의는 기반형식과 같은 이름의 메서드가 파생형식에 정의 되는 것이지요.

엄연하게 스코프가 다릅니다.


15.

OOP 삼대장 마지막 대장인 다형성에 대해서 알아보도록 하겠습니다.

'13번'에서 언급을 잠깐 드렸는데, 이 다형성을 사용하게 된다면

부모 클래스 형식의 포인터 변수로 자식 클래스의 객체들을 관리할 수 있게 됩니다.

하나의 변수로 여러 객체를 관리 할 수 있다는 것 자체가 참으로 매력적이지 않을 수 없습니다.


하지만 여기서 끝이 아닙니다. 

이렇게 설명이 끝나게 되면 다형성의 참된 장점에 대한 설명을 못할 뿐더러,

문제점만 야기시키게 되죠...


부모 클래스 형식에 포인터 변수로 자식 클래스의 객체들을 관리할 수 있게 됬만,

객체를 담는 그릇이 부모 클래스 형식이기 때문에

자식 클래스의 메서드를 사용하고 싶어도 그럴 수 없게 됩니다.

사용하는 곳에서 호출하는 메소드가 동일하지만, 

실제 동작하는 기능이 다르게 하기 위해서는 어떻게 해야할까요?


즉, 다양한 형식을 참조할 수 있다는 특징을 가지고 있어서 객체의 다형성이라고 불리우지만,

같은 변수로 호출할 지라도 실제 동작하는 구체적인 행위가 달라지기 위해서는 

어떻게 해야할까요?


이를 위해 C++에서는 하나의 기능을 제공하게 됩니다. 바로 'virtual' 키워드입니다.

이 virtual 키워드를 사용한다는 것이 메소드의 다형성에 대해 말하는 것입니다.


기반클래스에서 virtual 키워드를 사용하여 메서드를 선언하게 된다면

이는 '가상 메서드'라고 불리우게 됩니다...

내가 정의한 행위는 가상으로 정의한 것이다... 이렇게 생각하시면 될 듯합니다...


예를 통해 설명을 마무리 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Human
{
    string name;
    int age;
    int power;
public:
    virtual Eat()
    {
        cout<<" 학식 맛없다..."<<endl;
    }
};
 
class Sapzape
{
public:
    virtual Eat()
    {
    cout<<"소고기 냠냠"<<endl;
    }
};
 
void main()
{
    Human *= new Sapzape();
    h->Eat();
}
cs


이와 같은 코드가 있을 때 virtaul 키워드가 명시되지 않은 Eat() 메서드라면

분명 담고 있는 그릇이 부모클래스이기 때문에 맛없는 학식을 먹게 됩니다.


하지만, virtual 키워드를 명시함으로써 실존하는 객체의 메서드가 호출되도록 해줍니다.


이러한 이유는, virtual 키워드를 가진 메서드의 경우 메모리 할당시 따로 생성이 되며

실존하는 객체의 메서드의 메모리를 할당하게 되기 때문입니다. 


마지막으로 'virtual 메서드() = 0;' 이처럼 만드는 것은 뭘까요?

바로 순수 가상 메서드라고 부르지요...


이와 같은 순수 가상 메서드가 적어도 하나라도 있다면 이를 추상 클래스라고 부릅니다.

기반형식이 추상클래스라면 반드시 파생형식에서 기반형식에 있는 메서드를

재정의 해줘야 객체를 생성할 수 있게 됩니다.



15.5

다형성에 대한 썰을 풀다보니 상속과 관련하여 주의해야할 점에 대해 몇자 적고자 합니다...


우선 첫번째로, 상속이라고 하는 것은 기본적으로 기반 클래스로부터 파생된 클래스를 만드는 것이지요...

따라서 기반 클래스의 기본 생성자가 없을 때

파생 형식에서 반드시 생성자 이니셜라이저를 해야하한다는 주의 사항이 있습니다.


두번째로, protected 가시성에 대한 부분에 신경을 써야합니다.

protected 접근 지정 같은 경우에는 상속되는 클래스까지 가시성이 허용된다는 사실을

기억하시길 바랍니다.


16.

다형성이라는 장점을 통해 

부모 클래스 타입의 포인터 변수로 여러 자식 객체들을 담을 수 있게 되었습니다.

하지만 이때 문제가 발생할 수 있습니다. 자식 객체 고유의 메서드를 호출하고자 한다면?

이럴 때 바로 하향 캐스팅... 즉, dynamic_cast 를 사용하면 됩니다.


무식하게 강제 형변환을 하는 것이 아니라,

입력인자로 들어간 것이 파생 클래스의 형식이 맞다면

값 변화 없이 컴파일 에러가 발생되지 않게 형식만 변화 시켜주는 기능을 담당하고 있습니다.

중요한 것은 dynamic_cast를 사용하기 위해서는 입력인자로 사용되는 객체가

반드시 가상함수를 하나 이상 가지고 있어야 한다는 것입니다.


자세한 내용은 dynamic_cast 를 검색하여 찾아보시길 바랍니다.


지금까지 OOP 특징에 관한 내용을 설명했습니다...

힘차게 달렸군요...

아직 C++에 대한 특징들이 많이 남아있습니다. 달려봅시다~!



17.

'+' 라는 연산자는 더하는 기능을 수행하는 연산자입니다.

이와 같이 '-', '*', '/', '[]' 등등은 각자 고유한 기능을 가지고 있습니다.


그런데 말입니다,

C++에서는 피연산자 중에 최소 하나 이상이 '사용자 정의 형식'일 경우에 연산하는 기능에 대해서

중복 정의할 수 있도록 해줍니다.


무슨 x소리 이신지 감이 안잡히시죠...

예를 들어보도록 하겠습니다.


'1+2 = 3', '10-2 = 8' 과 같이 자연스럽게 연산이 되지요?

하지만 컴파일러 입장에서 사전에 

'int형 + int형은 int 형을 반환 하겠다'는 약속이 정의 되어 있기 때문에 

실제 프로그래밍 언어 상에서 연산자를 사용할 수 있게 되는 것입니다. 


위에서 말씀드린 것과 같이 '사용자 정의 형식'이 피 연산자중 적어도 하나 이상일 경우에

연산자 중복 정의를 할 수 있다고 했습니다.


왜 이런 말씀을 했냐면, 컴파일러는 사용자가 정의한 형식을 모르기 때문에

어떠한 연산을 하려 해도 알아먹지 못하게 됩니다.


따라서 중복 정의를 해줘야 하는 것이죠...


18.

'진짜 코드를 만들기 위해서 가상의 형식 인자와 기본적인 알고리즘을 정의한 코드'

를 가리켜 우리는 템플릿이라고 합니다.


즉, 템플릿은 가짜 코드라고 이해하시면 됩니다.

가상의 형식 인자를 템플릿 형식인자라고 부릅니다.

단, 공통적인 알고리즘을 가지고 있으니 가능한 문법입니다.


템플릿 문법은 특히 라이브러리를 제작할때 많이 사용됩니다.

우리가 어떠한 정렬 알고리즘을 작성한다고 했을때,

어떠한 형식에 대한 배열을 만들지 모르는데...

이렇게 형식에 구애받지 않고 우선 가상 형식의 인자를 받는 배열을 만드는 것이지요...


즉, 라이브러리를 제공하기 위해서는 사용자가 사용하는 형식인자에

맞게 움직이도록 템플릿을 사용하게 됩니다.




자... 지금까지 C++ 에 대한 총 정리를 진행해 봤습니다...

물론, 많이 부족하고 핵심만을 찝어 정리하지 못한 것 같아 아쉬운 마음이 들지만,


부족함은 실제로 겪어보며 채워나가기로 하고...

이상으로 키보드에서 손을 떨어뜨리도록 하지요 허허...


이상 삽잡이였습니다!