본문 바로가기
프로그래밍/컴퓨터 과학(CS)

C++ 에서의 OOP의 개념에 대한 설명(Abstraction, Encapsulation, Inheritance, Polymorphism)

by Hwan2 2020. 6. 21.
728x90
반응형

1. OOP란?? (Object Oriented Programming)

C++는 객체지향 언어 입니다.

해당 언어를 OOP라고 부르며 Object Oriented Programming의 약자입니다.

 

이 OOP에 대한 개념에 대해 설명해 볼까합니다.

왜냐하면..... 면접에서 물어볼 수 있는 질문이기 때문입니다.

 

 

기본적으로 C++의 OOP라 하면 다음과 같은 단어가 나옵니다.

1. Class

2. Object

3. Abstraction

4. Encapsulation

5. Inheritance

6. Polymorphism

 

이중에 Class와 Object는 Abstraction에 대한 내용에 포함 되므로

Abstraction, Encapsulation, Inheritance, Polymorphism에 대한 설명을 하겠습니다.

 

 

 

2. Abstraction

 

추상화란 개념입니다.

추상화?? 단어가 짐작이 가면서도 명확하게 설명하기 애매모호한... 그런 단어입니다.

사전에는 다음과 같이 정의합니다.

 

"추상화는 필요한 부분, 중요한 부분을 통합하여 하나로 만드는 것을 말합니다. "

 

Abstract class와 Interface를 생각하시면 좀 더 이해가 잘 될 것 같습니다.

저희는 어떤 클래스를 만들면 해당 클래스 목적에 맞는 맴버 변수와 맴버 함수를 만들게 됩니다.

 

예를들어 Human이라는 Class만들어 봅시다.

그럼 Human에는 다양한 함수들이 정의될 수 있습니다. move, talk, see, work 등.....

Human을 생각했을 때 여러가지 기능들을 생각할 수 있고 이를 Class를 통해 표현할 수 있습니다.

또한 이러한 기능들을 정의할 때 내부구현이 어떻게 되든 단어가 뜻하는 의미대로 프로그램이 작동하도록 설계합니다.

 

잘 이해가 안가신다고요??

자동차를 생각해 봅시다. 저희는 자동차가 어떤 원리로 움직이고, 어떤 매커니즘으로 설계되어있고, 내부적으로 

어떤 기능들이 작동하는지 잘 알지 못합니다.

단지, 저희는 자동차에 시동을 켜고 운전을 잘 할 줄 알면 되죠.

이런식의 설계, 이런 일련의 과정들이 바로 Abstraction(추상화) 입니다.

 

 

3. Encapsulation

말 그대로 캡슐화입니다. 감싼다라는 의미입니다.
무엇을 감싸냐? 클래스에 정의된 맴버 변수들을 감쌉니다.
왜 감싸냐??
바로 외부로부터의 노출을 최소화하기 위해서지요.
 

Abstraction에서 설명했던 말 중엔 "내부구현은 알필요가 없고 최대한 단어의 뜻대로 움직이면 된다." 했습니다.

즉, 내부는 건들지 말고 잘 설계된 기능들을 갖다 쓰면 되는 것입니다.

하지만 내부를 건든다면?? 100개의 톱니가 맞물려 있다 했을 때 10개 정도가 크기가 달라진다면??

해당 기계는 잘 작동될까요??

 

마찬가지입니다. 클래스의 맴버변수를 외부에 최대한 노출을 시키지 않기 위해서 private이라는 선언을 하게 됩니다.

이는 프로그램을 설계하는데 굉장한 도움이 되죠.

오류를 최대한 줄이기 때문입니다. 보안상으로도 더 안전하고요.

Encapsulation을 도와주는 키워드는 3가지가 있습니다.

1. private : 자기 클래스 내에서만 접근 가능.

2. protected : 자신 포함 자식 클래스 까지 접근 가능.

3. public : 어디서든 접근 가능.

이러한 기능을 제공하기 때문에 Encapsulation하다라고 말합니다.

 

 

4. Inheritance

상속이라는 개념입니다. OOP 언어의 가장 큰 특징 중 하나죠.
그럼 상속의 가장 큰 특징은 무엇이냐??
바로 재사용 입니다.
무엇의? 코드의 재사용이죠....
 
사람, 고양이, 개, 돌고래, 새 등.... 동물과 관련된 클래스를 만들어 본다고 가정해 봅시다.
해당 클래스마다 모두 일일이 고유의 독자적인 클래스를 만들건가요??
이는 매우 비 효율적이라고 말할 수 있습니다.
동물은 공통적인 특징을 갖고 있습니다. 
눈, 코, 입, 호흡 등..... 말이죠.
 
이런 것들을 일일히 각 클래스마다 정의를 한다?? 비효율적이죠....
그 공통적인 특징을 하나의 클래스로 정의하고 그 클래스를 상속받으면서
재사용하면 그 만큼 효율적인건 없을 것입니다.
 
 

5. Polymorphism

다형성이라는 뜻입니다. 굉장히 중요하죠.
면접을 볼 때 단골질문 이기도 합니다.
 
그럼 다형성이란 무엇일까??
사전에는 다음과 같이 나와있습니다.
 
"여러가지 형태가 존재한다."
 
겉보기엔 하나인것 처럼 보이는데, 동작은 상황에 따라 달라질 수 있다는 점이지요.
즉, 동일한 요청임에도 다르게 응답하는 것입니다.
 
이런것이 어떻게 가능하느냐??
Polymorphism은 크게 2가지로 나뉩니다.

1. Compile Time

2. Run Time 

 

1) Compile Time = Static Polymorphism

컴파일 타임에는 Overloading과 Overriding이 있습니다.

Overloading은 한 클래스에서 같은 이름의 매개변수와 반환 값이 다른 함수들을 여러게 정의하는걸 의미합니다.

Overriding은 재 정의의 의미로 상속 관계에서 자식 클래스가 부모 클래스와 똑같은 함수를 재 정의하는걸 의미합니다.

 

간단하게 코드로 본다면....

#include <iostream>

class A {
public:
    void fun1() { printf("A의 fun1\n"); }
};

class B : public A {
public:
    void fun1() { printf("B의 fun1\n"); }    //Overriding
    
    void add() {}   //Overloading
    int add(int n) {} //Overloading
    int add(int nint m) {} //Overloading
};

int main(void) {
 
    return 0;
}

이런식이 되겠습니다.

 

이처럼 같은 이름의 함수임에도 불구하고 넣는 값이나 의미에 따라 서로 다르게 출력이 됩니다.

하지만 특징은 Compile Time에 모두 결정되는 것들이란 사실이죠.

때문에 Static Polymorphism 이라고 불리기도 합니다.

 

 

2) Run Time = Dynamic Polymorphism

Run Time에 의미가 바뀐다는 말로 프로그램 실행 중 상황에 맞게 동작이 변한다는 뜻입니다.

이는 virtual 키워드를 이용한 virtual function overriding에서 진행됩니다.

 

코드를 보겠습니다.

#include <iostream>
#include <string>
#include <vector>

class Animal {
public:
    virtual void sound() = 0;
    void info(){
        std::cout << "동물은 숨을 쉽니다.\n";
    }
};

class Dog : public Animal{
private:
    std::string name;
public:
    Dog(std::string s) : name(s) {};
    void sound() { std::cout << "멍멍\n"; }
    void name_print() { std::cout << name << std::endl; }
    void only_dog() { std::cout << "이건 개 클래스\n"; }
};

class Cat : public Animal {
private:
    std::string name;
public:
    Cat(std::string s) : name(s) {};
    void sound() { std::cout << "냐옹\n"; }
    void name_print() { std::cout << name << std::endl; }
    void data() { std::cout << this << std::endl; }
    void only_cat() { std::cout << "이건 고양이 클래스\n"; }
};

int main() {

    std::vector<Animal*> v;
    v.emplace_back(new Cat("나비"));
    v.emplace_back(new Dog("멍멍이"));

    Cat* cat; Dog* dog;
    for (size_t idx = 0; idx < v.size(); idx++) {
        if (cat = dynamic_cast<Cat*>(v[idx])) {
            cat->name_print();
            cat->sound();
            cat->only_cat();
        }
        else {
            dog = dynamic_cast<Dog*>(v[idx]);
            dog->name_print();
            dog->sound();
            dog->only_dog();
        }
    }
    delete cat;
    delete dog;
    return 0;
}

 

upcast와 downcast를 통해 Dynamic하게 함수 출력을 해주고 있습니다.

물론 위 코드는 고칠점이 많습니다. (예를 들자면 virtual 소멸자도 없고, 일반적인 포인터로 받고 있지만 스마트 포인터로 받는게 더 좋고...)

하지만 이해를 돕기 위한 코드이니 너그럽게 넘어가시길 바랍니다.

 

이렇게 함수의 동작이 virtual로 인해 상황에 맞게 다르게 작동되는 것을 Dynamic Polymorphism 이라고 합니다.

 

 

 

 

OOP에 대한 설명을 적어봤습니다. 

면접장에서 Polymorphism이 뭐냐고 질문 했을 때 턱!! 막혔던 기억때문에 정리하게 되었습니다.

 

반응형

댓글


스킨편집 -> html 편집에서