'C++'에 해당되는 글 3건

 

 

안녕하세요 피터입니다.

 

많은 분들이 C 언어를 배우고 나서 C++ 를 배우기 시작 할 때 어려움을 겪곤 합니다. 

제 주변에도 그런 분들이 있어서 어떤 점 때문에 어려운지 물어봤더니 어디서부터 어떻게 시작해야 될 지 모르겠다는 답변이 가장 많았습니다. 

 

어쩌면 당연한 일인지도 모르겠습니다. 

비교적 간단한 문법 체계를 갖고 있는 C 언어와 달리 C++ 은 굉장히 다양한 패러다임이 녹아있으며 방대한 스케일을 자랑하는 언어이기 때문입니다. 

 

(물론 그렇다고 해서 C 언어가 가볍고 만만한 프로그래밍 언어라는 말은 아닙니다. 오히려 그 반대로 처음엔 쉬워보이지만 알면 알수록 어려워지는 언어입니다. C 언어는 역사가 긴 만큼 괴수 분들이 많이 서식하고 계십니다 :D)

 

그래서 우선은 C 언어와 C++ 두 언어가 어떤 부분이 다른지 간단하게 살펴보고 넘어가겠습니다.

 

Feature

C

C++

Paradigm

Procedural Language

Multi-Paradigm Language

Approach

Top down

Bottom up

Namespace

X

O

Inheritance 

X

O

Overloading

X

O

Polymorphism

X

O

Template

X

O

& reference

X

O

struct constructor

X

O

Memory allocation

malloc(), calloc(), realloc()

new

Memory deallocation

free()

delete

 

여러 차이점들이 있지만 눈여겨 볼 만한 부분은 C++ 의 Multi-paradigm 이라는 부분입니다. 

 

절차지향 프로그래밍(Procedure-Oriented Programming)을 지원하는 C 언어와 달리 C++ 은 C 언어의 절차지향 프로그래밍을 그대로 승계한 동시에 객체지향 프로그래밍(Object-Oriented Programming)일반화 프로그래밍(Generic Programming)을 모두 지원하는 언어입니다. 

 

C 언어의 문법을 그대로 계승했기 때문에 얼핏 보면 큰 차이가 없어 보여 쉽게 접근했는데,

코딩을 하면 할 수록 내가 알던 그 언어와 비슷하면서도 뭔가 잘못되고 있다는 느낌을 받게 되죠. 

(내거인듯 내거아닌 내거같은 너...)

 

그렇게 느낄 수 밖에 없는 것이 객체지향 프로그래밍에 대한 개념을 모르는 상태에서는 C++에서 새롭게 등장하는 객체지향 문법들을 온전히 이해 할 수 없기 때문입니다. 

 

게다가 처음보는 템플릿(template) 문법은 외계어에 가깝게 느껴집니다.

 

C++은 이처럼 여러 가지 패러다임을 지원하는 언어이니 만큼 제대로 사용하기 위해서 배워야 할 개념들이 많습니다.

그래서 배우면 배울 수록 겸손해지는 언어이기도 하죠. 

(괜히 개발자들 사이에서 본인이 C++ 고수라고 하는 사람은 상종도 하지 말라는 말이 있는 게 아닙니다...)

 

 

그렇다면 C++을 어떻게 배우는 것이 좋을까요?

우선은 기존의 Top-down 방식으로 사고를 했던 것에서 벗어나 Bottom-up 방식의 객체지향 개념을 먼저 익히는 것이 좋습니다. 

 

[객체지향] Object-Oriented Programming 핵심 개념의 이해

배경 데이터 흐름(Flow)에 기반한 절차지향적 프로그래밍 방법은 복잡한 로직을 갖는 큰 규모의 소프트웨어 개발에는 적합하지 않습니다. 하드웨어 성능이 폭발적으로 성장하면서 요구되어지는

gracefulprograming.tistory.com

 

객체지향 개념을 이해하고 나서 문법적인 면에서는 클래스(class)접근 제한자(access modifier), new, delete 등의 문법을 숙지하셔야 합니다. 

 

기존 C언어에서 malloc(), free() 대신 new, delete 를 사용하면 생성자(constructor)/소멸자(destructor)가 호출되는데 이러한 개념도 익혀두셔야 합니다. 

 

간단한 프로그램을 객체지향적으로 설계하고 구현하는 것을 반복적으로 하다 보면 이제 Bottom-up 방식으로 사고 하는 것이 어느정도 익숙해지게 됩니다. 

 

 

이제 슬슬 템플릿(template)을 배워보도록 합니다.

템플릿은 일반화 프로그래밍(generic programming) 패러다임으로 데이터 처리하는 로직을 데이터 타입에 비종속적으로 구현할 수 있게 해줍니다. 

(컴파일 타임에 주어진 타입 별로 클래스들이 분화됩니다)

 

템플릿 문법을 직접 사용해보는 것도 좋지만 STL(Strandard Template Library)을 잘 다루는 것이 매우 중요하기 때문에 STL의 컨테이너(container)들을 활용하는 연습을 많이 하는 것이 좋습니다.  

 

vector, list, map 등의 주요 컨테이너들을 능숙하게 다룰 수 있게 되면 특별한 경우를 제외하면 대부분의 로직을 구현할 수 있습니다. 

 

 

 

여러분의 공감과 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 주시면 감사하겠습니다.

-Peter의 우아한 프로그래밍

 

 

 

블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

댓글을 달아 주세요

GOTO 문에 대해서

2016. 11. 24. 18:07


GOTO문에 대해서는 다양한 의견이 있습니다.


간혹 개발자 커뮤니티 등의 사이트에서 GOTO문의 사용에 대해 격렬한 토론이 이루어지기도 합니다.


GOTO문을 적절히 사용하면 아무런 문제가 없다는 사람들과 GOTO문 자체를 쓰는 것을 극도로 혐오하는 사람들 간의 논쟁은 마치 물과 기름을 보는 듯 타협의 여지가 없어 보일 때가 많습니다.


하지만 중요한 점은 GOTO문으로 인해 야기되는 문제들이 정확하게 어떤 문제들인지 파악하고 효율적으로 사용할 수 있는 방법은 없는지 여러분들이 직접 고민하고 생각해볼 필요가 있다는 점입니다.


즉, 여러분이 GOTO문을 사용할지 안할지를 결정하는데 있어서 단순히 ‘누군가가 쓰지 말라고 해서’ 라는 이유로 사용하지 않거나, ‘누가 뭐라고 하던 나는 편하니까 그냥 쓰자’ 라는 생각으로 사용해서는 안된다는 말입니다.


GOTO문을 잘못 사용하면 코드의 가독성이 떨어지고 예상치 못한 논리적인 오류를 발생시킬 가능성이 있습니다. 하지만 가독성과 최적화를 모두 고려해도 GOTO문을 사용하는 편이 효율적인 상황에서는 사용하지 않을 이유가 없습니다.

(어짜피 컴파일러 내부에서는 for, while, switch 구문들이 GOTO 구문으로 변경됩니다)



선택은 여러분의 몫입니다.

유명한 개발자나 수업시간에 교수님께 들은 얘기를 아무 생각 없이 받아들이고 그대로 따라하는 것이 아니라 

자신만의 생각과 철학을 갖고 판단하시길 바랍니다.


- Peter의 우아한 프로그래밍

'' 카테고리의 다른 글

GOTO 문에 대해서  (0) 2016.11.24
0과 1밖에 모르는 CPU  (0) 2016.11.10
블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

Tag C++, C언어, goto

댓글을 달아 주세요

 

개요

hash_map은 비표준 Container인데 반해(stdext namespace에 포함) unordered_map은 C++11에서 STL 표준 Container로 추가되었으며, (사실 TR1부터 추가되었지만 C++11에서 좀 더 최적화가 이루어졌다고 합니다) hash_map과 거의 동일한 기능을 제공합니다.

 

MSDN의 hash_map 페이지에서도 표준인 unordered_map 사용을 권장하고 있으므로

이후에는 unordered_map 기준으로 이야기하겠습니다.

 

 

일반적으로 데이터 양이 많은 경우 map 보다 unordered_map은 성능이 더 좋습니다.

 

알고리즘의 차이로 데이터가 N 개일 때 map 은 O(logN)의 탐색 속도를,

unordered_map은 O(1)의 탐색 속도를 각각 갖기 때문입니다. 

 

그렇다면 문제는 '어느 시점부터' hash_map이 더 성능이 좋을까? 하는 것입니다.

 


 

기본 개념

map

map 은 기본적으로(대부분의 STL 컨테이너들이 그렇듯이) 레드블랙 트리(Red-Black Tree 이하 RB Tree) 기반으로 되어있습니다.

때문에 모든 데이터는 정렬되어 저장됩니다.

 

 

RB Tree는 검색속도가 빠른 Binary Search Tree(BST)에 Self-Balancing 기능을 추가한 것으로 AVL Tree 만큼 엄격하진 않지만

O(logN)을 보장하는 수준으로 Balancing 되어지는 특징을 갖고 있습니다. 

 

이러한 특성 때문에 입력되는 키 값의 분포가 고르지 못할 경우 balancing(node rotation)에 대한 비용이 계속해서 들어가기 때문에 

Insert / Delete 성능이 저하될 수 있습니다. (물론 최악의 경우에도 O(logN)의 성능은 보장됩니다)

 


unordered_map

 

unordered_map은 hash_table 기반의 hash container입니다. 

 

 

hash_table을 통해 참조를 하기 때문에 각 node들을 정렬할 필요가 없습니다. 

따라서 충분한 hash_table의 크기만 주어진다면 데이터 양이 많아지더라도 Search / Insert / Delete 성능을 꾸준하게 보장할 수 있습니다. 

 

 

성능 비교

 

map 과 hash_map 은 알고리즘 특성 상 키의 데이터 타입이 숫자일 때와 문자열일 때 현격한 차이를 보입니다. 

따라서 성능 비교도 숫자형 키를 사용할 때와 문자열 키를 사용할 때를 따로 비교해야 합니다. 

 

아래와 같이 container에 데이터를 insert 한 뒤 find를 반복적으로 수행한 시간을 기록합니다.

Test<T>
{
  T dict;
  for (int i=0; i<N; i++)
    dict.insert(make_pair(i, i));
  
  for (int i=0; i<N; i++)
    dict.find(i);
}

Key Type : Int 

 

X 축은 데이터 크기 N, Y 축은 탐색 1회에 걸린 시간(마이크로초) 입니다.

 

map은 O(logN) 의 시간복잡도를 보이며 급격히 소요시간이 증가한 구간은 캐시 미스로 인해 발생한 것으로 추측됩니다. 

그에 반해 unordered_map 은 O(1) 의 시간 복잡도를 보여줍니다. 

 

이처럼 숫자형 키를 사용할 경우 데이터의 양이 많아질수록 unordered_map이 월등한 성능을 보여주게됩니다.

 


Key Type : const char*

 

문자열을 키로 사용할 경우는 조금 복잡합니다. 

 

map은 insert 할 때 key를 기반으로 정렬하게 되는데 문자열 비교 함수의 성능은 문자열 전체를 hashing 하는 것에 비해 우수합니다. 

그것은 문자열 전체를 비교하지 않고 앞에서부터 한글자씩 차례로 비교하여 크고 작음을 가려내기 때문에 문자열의 길이가 길어지더라도 민감하게 반응하지 않습니다. 

key value들의 분포가 고르게 형성되어 있어 앞의 몇 글자만 비교해서 우열이 가려질 가능성이 있기 때문이죠. 

 

예를 들어 문자열 key의 집합이 다음과 같다고 했을 때, 

  • black, blue, cyan, gray, green, lime, magenta, maroon,
  • navy, olive, purple, red, silver, teal, white, yellow

 

앞의 1~2글자만 비교하면 대부분의 문자열 비교가 끝나게 됩니다.

 

반면 문자열의 길이가 길어지면 key를 hashing 하는 unordered_map의 경우 성능에 영향을 상대적으로 많이 받게 됩니다. 

 

따라서 문자열의 길이를 4, 8, 12, 16 으로 측정을 했을 때 아래와 같은 결과를 확인할 수 있습니다.

 

X 축은 데이터 크기 N, Y 축은 탐색 1회에 걸린 시간(마이크로초) 입니다.

 

기본적으로 숫자형 key와 비슷한 양상을 보이며, 문자열 길이가 커짐에 따라 교차점이 달라짐을 확인할 수 있습니다.

 

 

 

물론 key의 집합이 다음과 같이 접두사가 비슷한 경우라면 map도 문자열 길이의 영향을 더 크게 받게 됩니다. 

  • abnormal, abnomality, abnomalities, abnormalization

 

그렇기 때문에 키 값을 timestamp를 이용해 만들어 사용하는 경우에 문자열의 앞부분이 같은 표본이 대량으로 생성되어

비교함수의 성능이 떨어지므로 지양해야 합니다. (실제 사례)

 


결론

  1. 데이터가 많은 경우에는 unordered_map 이 map 보다 성능면에서 유리합니다. 
  2. 문자열을 키로 사용하는 경우 문자열이 길어질 수록 unordered_map 이 map에 비해 더 성능이 떨어질 수 있습니다. 
  3. 유사도가 높은 문자열 집합을 키로 사용하는 경우에 map 의 성능이 떨어질 수 있습니다. 

 

결론적으로 key를 이용하여 정렬을 해야 하는 경우를 제외하고 대량의 데이터를 저장할 때는 unordered_map 을 사용하는걸 추천합니다.

 

 


참고자료

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

https://en.wikipedia.org/wiki/Red%E2%80%93black_tree

http://veblush.blogspot.kr/2012/10/map-vs-unorderedmap-for-string-key.html 

블로그 이미지

친절한 Peter Ahn

IT 정보 공유, 프로그래밍 지식 공유

댓글을 달아 주세요

  • bammuri 2020.05.04 15:58  댓글주소  수정/삭제  댓글쓰기

    좋은 글 잘 봤습니다.
    질문이 있습니다.
    그렇다면 데이터의 갯수가 작을때 unordered_map의 성능이 떨어지는 이유는 무엇인가요?
    hash_table에서 탐색하는 시간이 포함되어서인가요?

    • 친절한 Peter Ahn 2020.05.04 16:03 신고  댓글주소  수정/삭제

      입력된 key에 대해서 hash value를 계산하는 비용과 hash_table 에서 탐색하는 비용때문입니다.
      ordered_map 에서는 key를 그대로 사용하기 때문에 차이가 발생합니다.
      데이터의 수가 많아지면 어느 순간 hash algorithm 비용보다 order algorithm 비용이 커진다고 보시면 되겠습니다.

  • 2021.07.30 20:54  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다