연산자 오버로딩


 연산자 오버로딩이란 내가 만든 사용자 정의 타입(class나 struct)에 관한 연산자를 정의하여 좀더 편하게 사용할 수 있게 만든 C++의 문법적 기능의 말한다. 


 문법

1
2
3
4
[리턴형] operator[연산자]( [연산 인자] ) {
//연산하는데 들어가는 로직
}
 
cs

example 1)

1
2
3
4
5
6
struct point {
    int x, y;
};
bool operator==(point a, point b) {
    return a.x == b.x && a.y == b.y;
}
cs

example 2)

1
2
3
4
5
6
7
struct point {
    int x, y;
    bool operator==(point b) {
           return x == b.x && y == b.y;
    }
};
 
cs


단항 연산자 오버로딩이항 연산자 오버로딩

 이 두가지 분류는 항의 갯수에 따라 단항 연산자 오버로딩인지, 다항 연산자 오버로딩인지가 정해진다. 오버로딩할 연산자의 항의 개수는 각각의 연산자들의 원래 항의 개수와 정확히 일치한다. 예를들어 /연산은 이항연산자 이므로 이항 연산자 오버로딩 해야하고 !는 단항 연산자 이므로 단항 연산자 오버로딩을 해야한다. 


더 알아 보기

이것 이외에도 아주 특이한 친구들이 몇몇 존재하는데 바로 ++나 --이다. 이 두가지 연산자를 오버로딩하는 방법에 관해서는 구글링 해보도록 하자. 현재 포스팅에서 다루기에는 너무 세세하게 들어간 것 같다.  

 
단항 연산자와 이항 연산자를 오버로딩한 모습은 아래와 같은데 -의 경우 단항연산자로도 쓰이고 이항 연산자로도 쓰이므로 좋은 예가 될 것 같아서 아래와 같이 오버로딩 해봤다. (23번째줄에서 사용) (생성자 만들기 귀찮아...)

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
#include <cstdio>
using namespace std;
struct point{
    int x,y;
 
};
point operator-(point p1){
    point ret;
    ret.x = -p1.x;
    ret.y = -p1.y;
    return ret;
}
point operator-(point p1,point p2){
    point ret;
    ret.x = p1.x - p2.x;
    ret.y = p1.y - p2.y;
    return ret;
}
int main() {
    point p1, p2;
    p1.x = 10, p1.y = 20;
    p2.x = 5, p2.y = 5;
    point p_one = -p1, p_two = p1-p2 ;
    printf("%d %d\n%d %d",p_one.x, p_one.y,p_two.x, p_two.y);
    return 0;
}
cs

output

- 10 - 20

5 15


컴파일 및 아웃풋


위의 23번째 줄은 다음과 동치이다. operator-를 일반 함수명이라고 생각한다면 더 이해하기 쉬울 것이다.

1
point p_one = operator-(p1), p_two = operator-(p1,p2);
cs



구조체나 클래스의 연산자 오버로딩전역 함수의 오버로딩


 일반적으로 전역 함수의 오버로딩은 단항 연산자의 경우 인자를 하나, 이항 연산자의 경우 인자를 두개 받는다. 이런 형태를 띄는게 전역 함수의 오버로딩인데 그 대표적인 예가 바로 위에서 보여준 예이다. 그렇다면 구조체나 클래스 안에서 구현한 연산자 오버로딩은 어떻게 다를까? 


  구조체나 클래스 안에서의 연산자 오버로딩의 경우 첫 번째 인자가 구조체나 클래스 인스턴스를 가르킨다고 생각하면 쉽다. 예제로 이해해 보자.

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
#include <iostream>
#include <cstdio>
struct point{
    int x,y;
     point operator-(){
        point ret;
        ret.x = -x;
        ret.y = -y;
        return ret;
    }
    point operator-(point p2){
        point ret;
        ret.x = x - p2.x;
        ret.y = y - p2.y;
        return ret;
    }
};
 
int main() {
    point p1, p2;
    p1.x = 10, p1.y = 20;
    p2.x = 5, p2.y = 5;
    point p_one = -p1, p_two = p1-p2 ;
    printf("%d %d\n%d %d",p_one.x, p_one.y,p_two.x, p_two.y);
    return 0;
}
cs

output

- 10 - 20

5 15


 연산자 오버로딩이 아까와는 달리 단항 연산자의 경우는 아무런 인자를 받지 않고, 이항 연산자의 경우 인자로 받는 항의 개수가 하나가 된다. 위에서 설명했다시피 자기 자신을 첫번째 인자라고 생각하면서 동작하게 되기 때문에 단항 연산자의 항은 자기자신으로 이미 한개가 정해져 있고, 이항 연산자의 경우는 첫번째 항으로 자기 자신이, 두번째 항으로 p2가 오게된 것이다. 
 23번째 줄을 아래의 문장으로 대치해도 같은 방식으로 동작하게 된다.(의미가 같다)
1
point p_one = p1.operator-(), p_two = p1.operator-(p2);
cs


더보기

 구조체나 클래스에 종속적인 배열 인덱스 연산자 오버로딩과 대입 연산자 오버로딩

 이 부분은 현재 시점에서 다루기 적절치 않은 부분이므로 개인적으로 공부하도록 합시다 ^ㅇ^

 메모리 관리나 레퍼런스 같은 복잡한 것들이 얽혀 있습니다. 실제로 C++의 라이브러리를 구현한다던가 하는 큰 규모의 프로젝트에서 사용될 법한 내용들은 처음 하시는 분들을 위해서 생략하도록 하겠습니다. 이런게 있구나 하고 넘어가시기 바랍니다. 



 C++은 그 기본부터 C에서 온 친구로서 사실상 C에서 사용할 수 있는 것들은 대부분 C++에서도 사용 할 수 있다. 오히려 C에서 불편하던 여러가지를 개선 시킨 것이 C++이라 할 수 있는데 이 때문에 C에서 불편하던 것들이 무엇인지 안다면 C++로 넘어가면서 어떤 방식으로 개선되었는지를 직관적으로 이해하기 쉽다. 

 

이번 포스팅에서는 C에서 C++로 넘어가는 와중에 필요한 지식을 간단히 설명하려 한다. 

 

여러분들이 C에 대해 대부분의 내용을 이해했다고 가정하고 설명하도록 할 것이기 때문에 C에 대한 내용은 언급만 하고 넘어갈 예정이다. 

 

1. namespace 

 

namespace란? 이름 공간. 함수, 변수, 클래스등의 하나로 묶어서 접근을 특정 이름만으로 할 수 있도록 제한하는 키워드 

 

 C에서 사용하는 여러 라이브러리의 속을 한번 뜯어봤다면 봤을 만한 함수나 변수명이 있을 것이다. __로 시작하는 이름들이 그것인데 이런 방식으로 사용하는 근본적인 이유는 함수명이나 변수명의 중복을 피하기 위해서다. (일종의 약속이라고 보면 된다.)

 

 이런 것들은 C의 태생적 한계 때문이기도 한데 함수의 경우 그 이름을 전역으로 밖에 선언하지 못하고 따라서 여러 라이브러리를 사용할 경우 이름이 겹치는 위험이 발생할 수 있기 때문이다. 이를 방지하기 위해 C에서는 static 키워드를 제공하는데 (특정 파일 안에서만 접근 가능 ) 이것으로는 부족한 것이 현실이다. 

 

 이를 해결하기 위한 좋은 방법으로 C++에서 제공하는 namespace기능을 이용할 수 있다. 이 기능을 사용하면 변수명이나 함수명이 겹치더라도 잘 처리할 수 있다. 

 

 이 namespace라는 것을 사용하는 방법은 아래와 같다. 

 

 

 

 일반적으로 C++로 프로그램을 짤 때 많이 사용하는 STL(Standard Template Library)은 모두 std라는 namespace에 묶여있으며 이 안에는 최대 값을 구해주는 함수가 있다. 이 함수는 STL library중에서도 algorithm 헤더파일에 들어 있는데 이를 사용하는 모습은 아래와 같다. 

 

1
2
3
4
5
6
#include <stdio.h>
#include <algorithm>
int main(void){
    int max = std::max(10,20);
    printf("%d",max);
}
cs

 

 

2. struct, class

 

 struct, class란? 여러 자료형을 한곳에 묶어 관리하기 위한 하나의 '구조'로 필요에 따라 접근을 제한 하고, 해당하는 자료를 처리하기 위한 루틴을 거치도록 만들 수 있다. 

 

 일반적으로 struct는 data에 관한 자료를 다루기 위해 사용하며, class는 그 외의 다양한 자료를 다루기 위해 사용한다. (ADT 같은 것들)

 

 C에서도 struct라는 키워드가 있다. 이는 C언어에서 제공하는 아주 강력한 기능으로 그 쓰임세는 정말 다양하여 모두 나열할 수는 없을 것이다. 하지만 그 대부분의 쓰임세는 struct의 특징인 여러 자료형을 한 곳에 묶어 (의미적으로, 공간적으로) 관리하기 편하게 만들었다는 점에서 기인한다. 하지만 C의 이런 방식은 한계가 존재하는데 바로  누구나 해당 자료형의 데이터에 접근하고 수정할 권한이 있다는 것이다. 

 

 ADT의 예를 들어보자. ADT에는 추상화라는 개념이 있다. '추상화'란 사용자가 그 내부의 복잡한 구현을 알 필요없이 그 기능들을 가져다 쓰게 함으로 써 실질적으로 필요로 하는 것을 구현하고 설계하는데 집중 하게하는 것이다. C에서 이를 쉽게 구현하는 법은 구조체에 담아 관리하고 이 구조체를 다루는 함수를 제공하는 방식인데 이 방식에는 약간의 문제가 있다.

 그 문제는 접근을 제한할 수 없기 때문에 발생하는데 대표적으로 struct의 field에 사용자가 접근하여 값을 바꾸는 것이다. 

 

ADT의 가장 대표적인 예인 double linked list를 예로 들어보자.  

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
 
struct linked_node {
    int data;
    struct linked_node * prev, * next;
};
struct linked_list {
    int size;
    struct linked_node * front* back;
};
bool linked_empty(struct linked_list * list){
    return list->size == 0;
}
int main(void){
    struct linked_list list;
    linked_init(&list);
    list.size = 1000;
    if(linked_empty(&list)){
        printf("empty");
    }else printf("not empty");
}
cs

 

 

위 코드의 linked_empty()라는 함수는 해당 리스트가 비어있다면 true를 비어있지 않다면 false를 리턴하는 C 코드이다.  만약 이 때 어떤 사용자가 main 처럼 마음대로 접근을 해버렸다고 해 버리자. 

 

 그렇다면 이 ADT는 원래의 역할을 제대로 다하지 못할 것이다. 

 

이 처럼 특정 데이터에 접근하는 것을 제한할 필요성이 있는데 이를 C++에서는 접근제어자로 해결할 수 있다. 접근 제어자는 크게 3가지 종류가 있는데 아래와 같다. 

  • private
    • 외부에서 접근이 불가능하고 내부에서만 접근이 가능하다. 
  • public
    • 외부에서 접근이 가능하다.
  • protected
    • 상속과 관련된 내용. 추후에 다룸

 위 키워드들은 아래와 같이 사용할 수 있다. 

 

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
27
28
29
#include <stdio.h>
 
 
struct linked_list {
private :
    int size;
 
    struct linked_node {
        int data;
        struct linked_node * prev, * next;
    }  * front* back;
public :
    linked_list() {
        size = 0;
        front = back = (linked_node *)0;
    }
    bool empty(){
        return size == 0;
    }
};
int main(void){
    linked_list list;
 
    list.size = 1000//에러
 
    if(list.empty()){
        printf("empty");
    }else printf("not empty");
}
cs

 13~16은 생성자라는 친구로 C에서는 없는 개념이다. linked_init에 해당하는 친구다. 

 

 24번째 줄에서 에러가 나는 것을 확인 할 수 있는데 private로 접근이 제한되었기 때문이다. 이렇게 잘못된 코드를 런타임 에러나 잘못된 동작이 아니라 컴파일 에러로 찾아 낼 수 있다는 것은 더 안전한 프로그램을 개발 할 수 있게 만들어준다. 

 

 상속이나 생성자, 소멸자에 관한 내용은 다음번에 다시 다루기로 하겠다. (지금은 필요 없음 ㅎ)

 

 c++에서 struct와 class는 큰 차이가 없는데 이는 c++이 객체지향적이기도하고 절차지향적이기도 한 과도기적 언어이기 때문이다. 다른 언어의 경우 이 두 키워드를 차이를 둬서 다루게 된다. 

 

 c++에서 두 키워드의 유일한 차이

  •  struct field의 default 접근제어자가 public
  •  class field의 default 접근제어자가 private

더 자세한 내용은 다음에 다루도록 하겠다. 

 

3. template

 

template란? template이란 단어 그 자체의 이름 그대로 형틀이다. 특정한 형틀을 가지고 자료형,함수등 새로운 것들을 찍어내는 것이라고 보면 된다. input으로 자료형을 넣으면 새로운 자료형, 함수, 클래스 등을 찍어 낼 수 있다.

template은 기존의 C언어에서 대응되는 개념이 거의 없는데 비슷한 것을 꼽아보라면 void *형이라고 보면 될 듯 싶다. 이는 void *보다 더 많은 정보를 담고 있는 친구이다. 

 

여러분은 C언어의 stdlib.h에 존재하는 qsort라는 함수를 사용한 경험이 있을 것이다. 이 함수는 quick sort를 구현해 놓은 친구로 어떤 배열형이든 받아 정렬해주는 함수이다. 이 함수의 원형은 다음과 같다. 

 

1
2
void qsort (void *base, size_t num, size_t size,
             int (*compare)(const void *const void *))
cs

혹시 모르는 사람들을 위해서 설명하자면 어떤 배열(base)과 그 배열이 담고있는 원소의 갯수(num), 그리고 원소 하나의 바이트수(size)와 각각의 배열을 어떻게 비교할지 결정하는 비교함수(compare)를 인자로 넘기면 그 배열을 quick sort로 정렬해주는 함수다. 

 

 C언어에서는 이런 임의의 배열을 표현할 방법이 없기 때문에 void *형이라는 꼼수를 사용해서 이를 구현해야 했다. 반면 C++에서는 이런 임의의 무언가를 표현할 좀더 직관적이고 안전한 방식을 제공하는데 그것이 바로 template이다. 

 사실 이런 template는 class부터 기본 타입, 때로는 상수도 들어갈 수 있으며 여러가지 장난질을 칠 수 있다. 이런 template으로 함수나 클래스등을 찍어 낼 수 있으며 이러한 자세한 내용에 관해서는 나중에 다루도록 하겠다.

 

 

template의 사용법은 대체로 "template이름<자료형>" 형태로 사용하게 된다. 위의 경우에는 template이름은 my_sort고 자료형들은 int나 double이 들어간 것이다. 때로는 이 <> 를 사용하지 않고도 사용할 수 있는데 이는 컴파일러가 자동으로 타입을 추론할 수 있는 경우에 한한다.   

 

template의 자세한 구현방법에 대해서는 다음에 더 자세한 내용을 다룰 것이다.

 

 

4. STL container

 

 STL이란? Standard Template Library의 약자로 C++에서 제공되는 표준화된 라이브러리다. 이 라이브러리는 쓰레드, 알고리즘, 특정한 ADT등 들을 다루기 위한 여러가지 모듈들을 이미 구현해놓았다.

 Container란? ADT들을 실질적으로 구현해놓은 것들이다.

 아마 다루고 넘어갈 것들로는 아래와 같다.

  • vector
  • deque
  • list
  • stack
  • queue
  • priority_queue
  • bitset
  • map
  • set
  • unordered_map
  • unordered_set
  • multiset
  • multimap
  • unordered_multiset
  • unordered_multimap

 

 

+ Recent posts