반응형
절차 지향 VS 객체 지향
절차 지향
- 절차 지향 모델링은 프로그램을 기능중심으로 바라보는 방식으로 "무엇을 어떤 절차로 할 것인가?"가 핵심이 된다.
- 즉, 어떤 기능을 어떤 순서로 처리하는가에 초점을 맞춘다.
객체 지향
- 객체 지향 모델링은 기능이 아닌 객체가 중심이 되며 "누가 어떤 일을 할 것인가?"가 핵심이 된다.
- 즉, 객체를 도출하고 각각의 역할을 정의해 나가는 것에 초점을 맞춘다.
절차 지향 VS 객체 지향
- 대형 프로그래밍의 경우, 많은 기능을 수반하기 때문에 절차 지향보다는 객체 지향이 적합
- 소형 프로그래밍의 경우, 작은 기능을 수반하기 때문에 객체 지향보다는 절차 지향이 적합
주의!: C++이 객체 지향 언어라고 해서 OOP가 목적이 되면 안된다.
C++를 쓰기 위해 읽기 편하고, 이해하기 편하고, 수정하기 편한 것이 목적일 뿐. OOP의 정의에 입각해서 프로그램을 짜는 것이 목적이 아니다. 프로그램을 통해서 가치를 창출하고 퍼포먼스를 내는 것이 목적이다.
다시 말해서, 가치를 창출하고 퍼포먼스를 내기 위해 OOP를 활용하는 것이지 OOP를 위해서 C++을 사용하는 것은 모순이다.
객체 지향 프로그래밍의 특징
추상화(abstraction = generalization = modeling)
- 객체들의 공통적인 특징(기능, 속성)을 도출하는 것
- 객체지향적 관점에서는 클래스를 정의하는 것을 추상화라고 할 수 있다.(클래스가 없는 객체지향 언어도 존재 ex.JavaScript)
캡슐화(encapsulation)
- 실제로 구현되는 부분을 외부에 드러나지 않도록 하여 정보를 은닉할 수 있습니다 (필요 없는 정보 노출 방지).
- e.g. getter: 멤버 함수를 통해 간접적으로 private 값을 노출
e.g. setter: 멤버 함수를 통해 간접적으로 private 값을 조절.
- e.g. getter: 멤버 함수를 통해 간접적으로 private 값을 노출
- 데이터를 보이지 않고 외부와 상호작용을 할 때는 메소드를 이용하여 통신을 합니다.
- has-a 관계: 하나의 클래스와 클래스 내부의 멤버 변수
- Person has a (std::string) name
- Person has a (int) floor
class Person
{
private:
std::string name;
int floor;
public:
Person(std::string name, int floor) : name(std::move(name)), floor(floor){};
std::string getName() const
{
return this->name;
}
};
+ 접근지시자: Public, Protected, Private
- public: 캡슐화 없이 완전 공개됩니다.
- protected: 캡슐화가 수행되며, 자식에게는 공개됩니다.
- private: 캡슐화가 수행되며, 자식에게도 숨겨집니다.
- 클래스 생성시 멤버 변수는 모두 private에 선언하고, getter와 setter를 추가하는 것이 좋습니다.
+ const, mutable
- const: 값을 상수로 선언할 수 있도록 도와주는 키워드로 값을 변경할 수 없도록 합니다. 이는 가독성을 증가시킵니다.
- mutable: 해당 멤버 변수를 const 멤버 함수도 변경할 수 있도록 만듭니다.
+ 익명 네임스페이스(unnamed/anonymous namespace) 활용
- namespace를 이용하면 객체끼리 특정 값을 공유할 수 있습니다
- Java 등의 언어는 static method를 이용합니다.
- 엄밀히 말해서 같은 cpp 파일 안에 있는 모든 코드가 익명 네임스페이스를 공유합니다.
- 절대 헤더 파일에 익명 네임스페이스를 선언하지 마세요.
namespace
{
std::string secret = "This is a secret code";
}
class Person
{
private:
std::string name;
int floor;
public:
Person(std::string name, int floor) : name(std::move(name)), floor(floor)
{
fmt::print("{}\n", secret);
};
std::string getName() const
{
secret = "secret code changed";
return this->name;
}
};
상속성(inheritance)
- 하나의 클래스가 가진 특징(함수, 데이터)을 다른 클래스가 그대로 물려받는 것입니다.
- 기존 코드를 재활용해서 사용함으로써 객체지향 방법의 중요한 기능 중 하나에 속합니다.
- is-a 관계: class Engineer : public Person과 같은 상속 구조를 말합니다.
- is-a, 즉 상속을 통한 확장은 코드를 복잡하고 유연하지 못하게 만드는 경향이 있습니다.
- 따라서 단순한 기능을 추가할 때, has-a로 충분할 때에는 가급적 has-a를 사용하세요.
- 참조: https://en.wikipedia.org/wiki/Composition_over_inheritance
class Person
{
private:
std::string name;
public:
explicit Person(std::string name) : name(std::move(name)){};
virtual std::string getName() const
{
return this->name;
}
};
class Engineer : public Person
{
public:
using Person::Person;
std::string getName() const override
{
return fmt::format("공돌이: {}", Person::getName());
}
};
+ explicit
- 자신이 원하지 않은 형변환이 일어나지 않도록 제한하는 키워드입니다.
- C++은 암시적 형변환을 수행하기에, 생성자 선언시 explicit를 붙이는 것이 선호됩니다.
- 암시적 형변환: 변환 가능한 타입끼리 암시적으로 형변환이 수행됨을 의미합니다.
다형성(polymorphism)
다형성(polymorphism)?
- 자식 클래스가 부모 클래스의 모습을 가질 수 있는 것(다양한 형태를 가질 수 있음)이 다형성입니다.
- 약간 다른 방법으로 동작하는 함수를 동일한 이름으로 호출하는 것으로, Overriding, Overloading이 있다.
- Overriding: 부모클래스의 메소드와 같은 이름을 사용하며 매개변수도 같되 내부 소스를 재정의하는 것이다.
- Overloading: 같은 이름의 함수를 여러 개 정의한 후 매개변수를 다르게 하여 같은 이름을 경우에 따라 호출하는 것이다.
가상함수 (virtual + override, final)
- virtual: 자식 클래스가 재구현하여 동작을 수정할 수 있습니다.
- 자식 클래스는 재구현시 override 혹은 final을 붙입니다.
- override: 재구현만 합니다.
- final: 재구현한 뒤, 만일 자신을 상속하는 클래스가 생겼을 때, 해당 클래스가 해당 멤버 함수를 재구현하지 못 하도록 합니다.
- override는 명시적으로 작성하지 않아도 컴파일은 가능합니다. 그러나 가독성을 위해 반드시 붙이도록 합니다.
+ 순수 가상함수란?
virtual void foo() = 0;
- 위와 같이 생긴 함수를 '순수 가상함수(pure virtual function)'라고 합니다.
- 함수의 정의가 이뤄지지 않고 선언만 한 것이며, 이렇게 선언된 순수 가상함수가 있는 클래스를 '추상클래스(abstract class)'라 합니다.
- 이러한 추상클래스는 객체를 만들지 못하고 상속으로써만 사용됩니다.
- 또한 상속받은 자식클래스는 무조건 해당 순수 가상함수를 override 해야합니다.
++ 언재 추상 클래스를 사용할까?
- 여러 클래스를 정의할 때 각각의 클래스의 공통된 요소를 나타내기 위해 대게 부모클래스를 이용합니다.
- 그렇다면 추상클래스는 언재 사용할까요?
- 추상클래스의 특징은?
- override 즉, 함수를 재정의 해 주지 않는다면 코드상에서 오류로 판단합니다.
- 떄문에 각 클래스 별로 꼭 정의해야하는 함수를 순수 가상함수로 지정해두어 이런 실수들을 방지할 수 있습니다.
- 추상클래스의 특징은?
- 추가로, 추상클래스를 정의할 때 맴버변수 없이 순수하게 순수 가상 함수로만 이뤄진 클래스를 디자인하는 것이 좋습니다.
- 따라서, 대체로 인터페이스 클래스를 하나 만들어 놓고, 그 외에 implementation한 클래스(구현이 완료된 일반적인 클래스)를 다중 상속을 통해 사용하곤 합니다.
class A {
public:
virtual void foo() = 0;
virtual void talk() = 0;
virtual int number() = 0;
};
동적바인딩(Dynamic Binding)
- 가상 함수를 호출하는 코드를 컴파일할 때, 바인딩을 실행시간에 결정하는 것.
- 파생 클래스의 객체에 대해, 기본 클래스의 포인터로 가상 함수가 호출될 때 일어난다.
- 함수를 호출하면 동적 바인딩을 통해 파생 클래스에 오버라이딩 된 함수가 실행
- 프로그래밍의 유연성을 높여주며 파생 클래스에서 재정의한 함수의 호출을 보장(다형 개념 실현)
클래스 디자인 가이드라인
멤버 변수는 항상 private로 선언하세요.
- 필요한 경우 getter, setter를 작성하세요.
멤버 함수에 붙어있는 const를 제거하지 맙시다.
- getter는 항상 const로 작성하세요.
- 단, 외부 코드에서 수정을 허용하는 경우 const를 제거하세요.
- const를 하기 위해 const_cast를 사용할 생각은 하지 마세요.
이동 연산이 가벼운 경우 생성자에서 이동 연산을 수행하세요.
가능한 한 선언 코드에서 초기화합시다.
- 기본 값을 넣을때 생성자를 사용하지 마십시오.
- 기본 값은 변수 선언시에 바로 넣을 수 있습니다.
가능한 한 기존에 존재하던 생성자를 이용합시다.
- 생성자는 다른 생성자(부모의 생성자도!)를 호출할 수 있습니다.
- 코드의 중복을 피하기 위해 가능한 한 다른 생성자를 호출하도록 합니다.
소멸자를 만들지 마세요.
- 소멸자가 구현되면 기본 이동 생성자가 없어지는 등의 부작용이 생깁니다.
- 소멸자를 만들지 않으면 나머지 특수 함수에 아무런 변화가 발생하지 않습니다.
- 소멸자를 만든 경우 나머지 특별 함수(생성자, 복사생성자, 복사대입연산자, 이동생성자, 이동대입연산자)를 모두 만들어라.
class container
{
public:
container() noexcept;
~container() noexcept;
container(const container& other);
container(container&& other) noexcept;
container& operator=(const container& other);
container& operator=(container&& other) noexcept;
};
- 하단의 도표를 보아 알 수 있듯이 소멸자는 관리하기는 매우 어렵습니다.
- 가능한 경우 항상 소멸자를 만들지 마세요.
참고
- [ACODOM] 객체지향: http://www.incodom.kr/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5
- [씹어먹는 C++] 내가 만드는 String 클래스: https://modoocode.com/198
- [씹어먹는 C++] C++ 표준 문자열 & 부모의 것을 물려쓰자 - 상속: https://modoocode.com/209
- [씹어먹는 C++] 가상(virtual) 함수와 다형성: https://modoocode.com/210
- [코드없는 프로그래밍] OOP, Class 소개: https://www.youtube.com/watch?v=5l-B2gbSoD0&list=PLDV-cCQnUlIYTBn70Bd822n2nlJwafCQE&index=2
- C++ 순수 가상 함수란? 추상 클래스란??(pure virtual function, abstract class): https://hwan-shell.tistory.com/223
반응형
'Study: Software(SW) > SW: Language' 카테고리의 다른 글
[C++] 템플릿 메타 프로그래밍 (Template Meta-programming) (0) | 2022.06.14 |
---|---|
[C++] 템플릿 (Template) (feat. Template Meta-programming) (0) | 2022.06.14 |
[C++] STL 컨테이너: 자료구조를 이해하고 사용하자! (C++ 표준 라이브러리) (0) | 2022.06.13 |
[C++] 포인터(*), 레퍼런스(&)와 상수(Const) + Smart Pointer, Constexpr (0) | 2022.06.13 |
[C++] 자료형 데이터 타입(Data Type): 고정 길이 정수, 형변환(Type Casting) (0) | 2022.06.13 |