본문 바로가기

Cpp/Story

C++ 이란?

* 바쁘신 분들을 위한 3줄 요약

1. C++은 1985년에 처음으로 탄생한 언어이다.

2. C에 객체지향을 얹은게 C++이다.

3. 개발자의 실수로 취약점이 발생했을 때, C++은 교정해주지 않는다.

 

 

 


소개 (Introduce)

C++언어는 1985년에 Bjarne Stroustrup(비야네 스트로스트룹)이라는 덴마크 컴퓨터 과학자가 C언어를 확장하여 만든 고급(High Level) 범용(General Purpose) 프로그래밍 언어입니다. 1998년에 ISO/IEC 14882:1998로 처음 표준화 되었습니다. C언어를 확장했기 때문에 C++은 대부분의 C구문을 상속합니다.  

 

글 작성 시점(2023-02-05)에서의 C++은 C++23까지 Preview release가 되었으며, C++20이 Stable release 되었습니다. C++11을 주로 써왔던 저로써는 사실 어떤 점이 다른지 자세하게 살펴본 적은 없었기 때문에 이번 블로그 운영을 통해서 어떤 점이 다른 지도 찬찬히 살펴볼 예정입니다.

 

사실 C++ 보다는, python이나 javascript 같은 interpreter language 혹은, Rust나 Go 등의 Compiled language가 대세이기는 하지만, 어찌 되었건 C++는 하드웨어와 매우 밀접한 언어로써, 성능 최적화와 한정된 하드웨어에서 극강의 효율성을 발휘하는 장점은 여전합니다. 이러한 장점으로 인해 과거에는 수많은 프로그램들이 C++로 만들어졌으며, 그것을 유지보수하는 기간을 생각하면 완전히 사라지까지는 매우 많은 시간이 걸릴 것입니다. 그러니 오래된 언어(1985년)에 나온 언어를 가지고 글을 쓰는 것에 대해서는 문제가 없다고 생각합니다. 

 

 

 


철학 (Philosophy)

C++의 철학은 C++언어로 무언가를 만드는 개발자를 위한 것이 아닌, C++자체를 버전 업 하는 사람들이 C++17, C++20을 내놓으면서 고려하는 철학인 것입니다. 철학이 뭐가 중요하나 싶을 수도 있겠지만...언어에 대한 철학을 저는 python으로 처음 접했었는데요, 철학을 본 이후로 C++의 철학도 찾아보고 많은 인상을 받게 되었습니다. python의 철학도 매우 재밌으니 한번 알아보시기 바랍니다.

(아래는 필자 스스로 이해하기 쉬운 방향으로 번역하였습니다)

 

It must be driven by actual problems and its features should be immediately useful in real world programs.

현실 문제에 의해 주도 되어야 하며, 기능은 현실 세계의 프로그램에서 즉시 유용해야 한다.

가장 첫 문장으로 나온 만큼 C++에서 매우 중요한 철학이라 생각됩니다. 현실세계의 문제에 집중해야 한다는 뜻입니다.

 

Every feature should be implementable (with a reasonably obvious way to do so).

모든 기능은 합리적이고 분명한 방법으로 구현 할 수 있어야 한다.

합리적이고 분명한 방법의 중요성은 개발자들도 신경써야 할 부분입니다.

 

Programmers should be free to pick their own programming style, and that style should be fully supported by C++
C++ 개발자는 자신만의 프로그래밍 스타일을 자유롭게 선택할 수 있어야 하며, 해당 스타일은 C++에서 완벽하게 지원되어야 한다.

나중에는 C++에서 구현이 불가능한 프로그래밍 스타일이 생길까요?

 

Allowing a useful feature is more important than preventing every possible misuse of C++.

C++의 유용한 기능을 허용하는 것이, C++의 오용 가능성을 모두 방지하는 것보다 더 중요하다.

의외의 철학이었던 것 중에 하나입니다. 견고함 보다는 가능성을 추구하는 C++입니다.

 

It should provide facilities for organising programs into separate, well-defined parts, and provide facilities for combining separately developed parts.

프로그램을 잘 정의된 별도의 부분으로 구성하기 위한 기능을 제공해야 하며, 또한 별도로 개발된 부품들을 결합할 수 있는 기능을 제공해야 한다.

모듈화의 중요성을 강조합니다.

 

No implicit violations of the type system (but allow explicit violations; that is, those explicitly requested by the programmer).

C++은 타입 시스템에 대한 암시적 위반이 없다. 그러나 명시적인 위반, 즉 프로그래머에 의해 명시적으로 요구된 위반은 있다.

암시적 형변환이 의도된 형변환이 아니면 overflow가 발생할 수 있습니다.

 

User-created types need to have the same support and performance as built-in types.

C++에서 프로그래머가 정의한 타입은 기본 타입과 동등한 지원과 성능을 가져야 한다.

struct는 class public access 형태이며, C++에서는 struct와 class가 거의 동일한 performance를 가집니다.

 

Unused features should not negatively impact created executables (e.g. in lower performance).

사용되지 않는 기능은 프로그램이 실행될 때 성능저하와 같은 부정적인 영향이 없어야 한다.

Compiler가 생각보다 많은 최적화를 해줍니다. 사용되지 않는 기능을 제거할 뿐만 아니라, 나눗셈 같은 연산도 최적화를 진행합니다.

 

There should be no language beneath C++ (except assembly language).

C++언어 아래에는 어셈블리 언어만이 있어야 한다.

C++을 Compile하면 asm으로 변환된 후, machine code로 최종 변환됩니다.

 

C++ should work alongside other existing programming languages, rather than fostering its own separate and incompatible programming environment.

별도로 호환되지 않는 프로그래밍 환경을 조성하기보단, 기존의 다른 프로그래밍 언어와 함께 동작해야 한다.

python 프로그래밍을 하다보면, 특정 연산의 오버로드가 걸리는 경우는 C++로 구현하고 연결하는 경우도 있습니다.

 

If the programmer's intent is unknown, allow the programmer to specify it by providing manual control.

어떤 코드의 의도를 알 수 없는 경우, 프로그래머에게 수동제어를 제공하여 그 의도를 특정하게 한다.

Debugger가 생각납니다.

 

 


언어 (Language)

C++언어의 창시자인 Stroustrup는 C++를 이렇게 설명합니다.

효율적이고 우아한 추상화를 구축하고 사용하기 위한 경량화 프로그래밍 언어
하드웨어 액세스추상화를 모두 제공하는 것이 C++의 기초.
이를 효율적으로 수행하는 것이 다른 언어와 구별되는 점.

객체지향의 간단한 컨셉과 객체의 메모리 저장에 대해 간단히 설명 드리겠습니다.

 

객체 (Object)

C++은 객체 지향 프로그래밍(OOP) 기능을 도입했습니다. OOP언어에는 일반적으로 4가지 기능이 존재합니다.

1. 추상화 (Abstraction) : 현실 세계의 정보들 중, 문제를 해결하기 위해 필요한 정보만 Object에 담는 기법입니다.

    - 사용하는 이유 : 필요한 정보만 모아서 문제를 해결하는 것이 가장 빠르고 정확한 방법.

    - 개념 사용법 : 풀어야 할 문제에 대해 필요한 개념들을 value, method로 표현한 후 객체들로 묶음.

    - 예 : 냉장고가 현관을 통과할 지 고민할 때, 냉장고의 크기를 직육면체로 추상화. 가격과 성능은 불필요 정보

 

2. 캡슐화 (Encapsulation) : value와 method를 public과 private으로 구분하여 private은 노출시키지 않는 기법입니다.

    - 사용하는 이유 : 안정적인 인터페이스 설계

    - 예 : 디지털시계는 사용자가 시계를 보는 데는 문제가 없으면서도, 내부 구조가 어떻게 돌아가는지 볼 필요가 없음

    - 예 : 모터 구동 A와, 계기판 B가 제작될 때, 서로 정보를 주고받을 interface만 설계할 뿐(public), 내부 구조(private)는 알 필요 없음.

    * 캡슐화는 인터페이스 설계 편의성이 목적이기 때문에, 보안의 위협을 막을 수는 없습니다.

 

3. 상속 (Inheritance) : 객체 간의 계층을 두어 한쪽에서 다른 쪽으로 value, method를 받아올 수 있습니다.

    - 개념 사용법 : 추상화를 하다 보면 공통된 개념으로 묶이는 부분이 존재. 이 개념을 상위 개념으로 하여 기존 개념들에 상속함.

    - 사용하는 이유 : 코드의 중복되는 부분을 줄여주고, 유지보수가 쉬워지기 때문.

    - 예 : 동물원을 모델링한다고 하면 원숭이, 침팬, 사자 등 다양한 동물을 따로 구현하지 않고 동물 개념을 객체로 만들어 상속함.

    - 예 : 동물들에게 밥 먹는 method를 추가하고자 한다. 기본 객체가 없었다면 동물 객체마다 추가로 작성해야 한다.

 

4. 다형성 (Polymorphism) : 객체들에 공통된 interface를 하나로 묶어 공통의 객체를 설계 한 후, 다양한 객체에 상속시킬 때는 객체마다 다르게 작동하게 하는 것입니다.

    - 개념 사용법 : 기본 객체를 상속받은 후, 특징을 가진 method, value를 overriding 해서 본인만의 특성을 가지게 하는 것

    - 사용하는 이유 : 상속받은 객체들이 overriding이 없다면 특성별 method명이 객체마다 달라지게 된다.

    - 다형성 있는 예 : 모든 동물들은 동물 객체를 상속. 사자.다리() -> 4, 뱀.다리() -> 0, 참새.다리() -> 2

    - 다형성 없는 예 : 동물 객체를 상속받았지만 서로 다른 method 필요. 사자.사자다리() -> 4, 뱀.뱀다리() -> 0, 참새.참새다리() -> 2

    4-1. Compile time Polymorphism : 상속된 method의 입력 type, 혹은 변수 개수가 달라 compile 단계에서 method가 확정.

    4-2. Run time Polymorphism : 상속된 method의 입력 type, 변수개수가 같아, run time 단계에서 method가 확정.

 

* 정리

추상화 : 개념을 담을 객체 자체를 설계.

캡슐화 : 객체 인터페이스 설계.

상속 : 객체 간의 공통부분 설계.

다형성 : 객체별 상속된 영역 특징 설계.

 

결국, OOP는 객체를 어떻게 설계하냐에 대한 철학인 것입니다.

 

* 쉬운 설명 : 게임을 해보신 분들은 Class를 "직업"이라는 용어로 써본 적이 있을 것입니다. 캐릭터가 같은 직업을 가지고 있다면 같은 스킬을 가지고 있지만(상속), 다른 직업끼리는 스킬이 다르게 설정됩니다.(Class 간의 다형성). 심지어 같은 직업끼리도 아이템이나 스킬 세팅이 서로 다를 수 있습니다(Class 내 다형성). Class는 직업 그 자체를 의미하지만, 직업을 가지고 있는 캐릭터는 객체(Object)가 됩니다.

객체 저장 (Object storage)

C언어와 마찬가지로, C++은 4가지 유형의 메모리관리를 지원합니다.

 

1. Static storage duration objects : main() 전에 생성되고 main() 종료 후 소멸 됩니다.

2. Thread storage duration objects : thread 생성 직전에 생성되고, thread join 후 소멸됩니다.

3. Automatic storage duration objects : 블록{ }의 내 변수가 일반적으로, 선언 시점에 생성되고 블록이 끝나면 소멸됩니다.

4. Dynamic storage duration objects : 수명이 동적입니다. new로 할당하고, delete로 삭제할 수 있습니다.

    * new를 할당한 후 delete를 하지 않으면, 더 이상 사용하지 않는 변수가 프로그램이 끝날 때까지 메모리를 소유하게 됩니다.

      이러한 현상을 메모리 누수(memory leak)라 합니다. 이러한 현상을 막기 위해 smart pointer를 제공합니다.

    * smart pointer 종류는 unique_ptr, shared_ptr, weak_ptr이 있습니다. (auto_ptr은 C++11 이후 사라짐)

    * C++11부터는 make_unique, make_shared를 사용하는 것을 권장하고, new(+스마트 포인터)를 직접 사용하지 말 것을 권고합니다. 

    * 1,2,3번은 Stack memory에 저장되어 나중 생성된 객체 순으로 먼저 소멸됩니다.

    * 4번은 Heap memory에 저장되고, 동적으로 할당, 저장됩니다.

 

아래는 C++의 추가적인 기능 설명입니다.

템플릿 (Templates)

C++ 템플릿은 매개변수의 타입에 따라 함수, 클래스를 생성하는 도구로써, Generic Programming을 가능하게 합니다.

    * Generic Programming이란? : 알고리즘 실체화(instance)를 바로 하지 않고, 매개변수의 type이 정해질 때 하는 것.

    * 매크로(#define)와 템플릿은 다릅니다. 매크로는 code 내 String 대체 기법으로, debugging이 되지 않습니다.

람다 식 (Lambda Expressions)

익명함수입니다. iteration에서 쓸만한 때가 꽤 있습니다.

예외처리 (Exception Handling)

run time 상황에서 문제가 발생한 경우, 문제를 처리할 수 있는 위치로 전달되는 데 사용됩니다.

try - catch를 활용해서 예외처리를 할 수 있습니다.

문제를 의도적으로 발생시키고 싶은 경우 throw 키워드를 사용합니다.

열거형 (Enumerated types)

열거형이란, 나열된 변수에 숫자를 순서대로 자동할당한 자료형입니다.

typedef와 같이 사용하면 enum 선언을 좀 더 간단하게 할 수 있습니다.

 

여기까지 언어에 대한 특징을 살펴보았습니다. C++언어는 많은 기능을 지원하고, 하드웨어를 직접 컨트롤 할 수 있어서 참 좋아 보입니다. 그렇다면 단점은 없을까요?

 

 

 


비판 (Criticism)

우선적으로 비판받는 원인은 너무 방대한 언어가 되었다는 점 입니다. 이로 인해 생기는 비판은 다음과 같습니다

 

1. 다중 상속, 과도한 Macro 등 코드의 복잡도를 올리는 다양한 기능들이 존재하여 유지보수에 악영향

2. 메모리 누수 등의 취약점을 컴파일러가 사전에 고지하지 않음.

3. reflection 부족

4. 최근 프로그래밍 언어에서 지원하는 garbage collector 부재

5. 오래 걸리는 컴파일 시간.

6. 템플릿의 오류의 경우 장황한 디버그 메시지.

 

 

그래도 개발자가 제대로 코딩을 한다면, 한정된 하드웨어에서 강력한 프로그램을 만드는데는 최고의 언어일 겁니다.

 

 


* reference

Evolving a language in and for the real world: C++ 1991-2006 in 3page

http://stroustrup.com/hopl-almost-final.pdf 

https://github.com/isocpp/logos

https://en.wikipedia.org/wiki/C++ 

https://en.wikipedia.org/wiki/Generic_programming

'Cpp > Story' 카테고리의 다른 글

C++에서 Compiler는 어떻게 동작할까?  (0) 2023.03.02