Skip to main content Link Search Menu Expand Document (external link)

cpp03 - Class inheritance

cpp module 03에서는 클래스 상속을 구현한다. 클래스 상속은 기존 클래스를 속성을 다른 클래스가 받고 의도에 맞게 수정하여 사용하거나 클래스 간의 종속 관계를 형성하여 객체를 조직화한다.

ex00

ClapTrap이라는 클래스를 구현한다. 앞으로 구현할 클래스들의 부모 클래스이다.
이 클래스는 name, HP, EP, AD 속성을 priavte으로 가지며, attack, takeDamage, beRepaired라는 멤버 함수를 public으로 가진다.

ex01, ex02

ClapTrap 클래스를 상속받는 ScavTrap과 FragTrap 클래스를 만든다.
상속받는 두 클래스를 구현하다보니 ClapTrap의 속성이 private로 구현되어 접근할 수 없었다. 그래서 getter와 setter를 ClapTrap에 구현하기로 한다. ClapTrap의 속성을 protect로 변경하여 접근을 허용하는 방법도 있다. 하지만 속성의 허용 범위를 두어야 하기 때문에 setter에서 검사 후 적용하는 방법을 사용하기로 한다. getter와 setter는 외부에서 접근하지 못하도록 protected로 설정한다.

ex03

ScavTrap과 FragTrap을 상속받는 DiamondTrap 클래스를 구현하는 과제이다. ScavTrap과 FragTrap은 앞에서 구현했듯이 모두 ClapTrap을 상속받고 있다. 그렇기 때문에 두 클래스를 상속받는 일은 문제를 발생시킨다. DiamondTrap이 ClapTrap의 속성이나 멤버 함수를 사용하고자 하면 ScavTrap의 ClapTrap을 참조하는지 FragTrap의 ClapTrap을 참조해야하는지 헷갈리기 때문이다. 이때 virtual 키워드를 사용하여 모호성을 해결한다. virtual 키워드는 객체에 대한 참조를 컴파일 시점이 아닌 런타임 시점으로 미룬다. 이를 각각 정적 바인딩과 동적 바인딩이라고 부른다. 객체가 호출되는 시점에 이동할 메모리 주소를 정적 바인딩은 컴파일 시점에 저장해주고, 동적 바인딩은 컴파일 시점에 메모리 공간을 확보한 다음 런타임 시점에 결정한다.

cpp04 - Abstract class, Interface

ex00 - override function

Animal 클래스를 상속받는 Cat 클래스와 Dog 클래스를 구현하는 과제이다. Cat과 Dog 클래스는 Animal의 makeSound 멤버 함수를 override하여 사용해야 한다. 그렇기 때문에 Animal 포인터 변수에 Cat이나 Dog 객체 포인터를 주고, makeSound 멤버 함수를 호출하면 Animal 클래스의 makeSound가 아니라 Cat과 Dog의 makeSound가 호출된다.

ex01 - deep copy

Brain 클래스를 구현하고, Cat과 Dog 클래스가 Brain 객체 포인터를 private으로 가지도록 한다. 이때 문제는 복사 생성자와 할당 연산자의 Brain 복사이다. brian이 할당되어 있다면 제거하고 복사하려는 객체가 가진 Brain과 똑같은 Brain을 생성해야 한다. 똑같은 Brain 객체를 생성하기 위해서 Brain의 복사 생성자를 사용했다.

ex02 - abstract class

이전에 구현한 Animal 클래스를 추상 클래스로 구현하는 과제이다. 추상 클래스에는 하나 이상의 순수 가상 함수가 존재해야 한다.
순수 가상 함수란 함수의 정의가 이루어지지 않고 선언만 되어있는 함수이다. 그렇기 때문에 순수 가상 함수를 가진 클래스틑 객체로 생성되지 않고 상속에 사용된다. 상속받는 자식 클래스는 무조건 순수 가상 함수를 override 해야한다. 순수 가상 함수를 override 하지 않으면 순수 가상 함수가 남아있기 때문에 객체로 생성되지 않는다.
이런 추상 클래스를 사용해야 하는 이유는 자식 클래스에서 반드시 선언되어야 하는 함수를 알려주기 위함이다. 만약에 가상 함수로 정의되어 있다면 자식 클래스에서 override 하지 않았어도 코드 상의 오류로 판단되지 않는다. 이런 실수를 방지할 수 있다.

ex03 - interface

추상 클래스와 인터페이스를 구현하여 프로그램을 구현하는 과제이다. 인터페이스는 순수 가상 함수로만 이루어진 클래스이다. 추상 클래스에는 멤버 변수와 멤버 함수를 자체적으로 가질 수 있지만 인터페이스는 순수 가상 함수로 이루어져 있다. 그렇기 때문에 자식 클래스가 사용하지 않는 부모 클래스의 데이터를 가지게 되는 추상 클래스보다 꼭 선언해야하는 함수를 정의한 인터페이스를 사용한다.

AMateria 추상 클래스를 정의하고 자식 클래스로 Ice와 Cure를 정의한다. AMateria 클래스에서 순수 가상 함수로 선언되는 함수가 아니라면 자식 클래스에서 호출된 경우를 생각하여 함수를 정의해야 한다.

ICharacter 인터페이스를 정의하고 Character 클래스를 구현한다. Character는 4개의 slot을 가지고 있고 Materia를 저장할 수 있다. 만약에 slot이 가득한 상태에서 새로운 Materia를 equip한다면 아무일도 일어나지 않고, 존재하지 않는 Materia 클래스를 equip하려는 상황도 마찬가지이다.

Character 클래스는 반드시 deep copy 되어야 하고 복사 또는 제거될 때 Materia들을 반드시 delete 해야한다. Character에서 equip, unequip하는 Materia를 모두 가지고 있다가 나중에 delete 해야하기 때문에 리스트에 데이터를 가지고 있기로 한다.
Materia가 생성되어 equip된다. 그런데 하나의 Materia가 여러 Charater에 equip되는 일은 부자연스럽기 때문에 Materia에는 is_equiped 변수가 있다. 이 변수로 Charater가 이미 장착된 Materia를 장착하지 않도록 한다.
만약에 Character가 덮어씌워지거나 제거되는 경우에는 인벤토리에 있는 Materia를 delete 해야한다. Materia를 자유롭게 delete하라고 명시되어 있으므로 나는 Character의 trash에 버리기로 한다. copy 또는 destroy 되는 경우에 trash는 clear 된다. MateriaSource에서 모든 Materia를 관리하는 경우에는 문제가 발생할 수 있다. 만약에 MateriaSource가 Character보다 먼저 제거되는 경우에는 Character가 equip하고 있던 Materia가 모두 제거된다. 그러므로 Materia는 Character에서 관리해야 한다. MateriaSource는 Materia를 생성하는 역할만 한다.

cpp05 - Exception

cpp05는 std::exception 클래스를 상속받는 예외 객체를 재정의하고 예외 상황에서 사용해본다.
예외 처리는 try~catch 구문과 throw로 구현된다. 예외가 발생할 수 있는 영역을 try로 감싸고, 이 영역에서 throw로 예외가 던져진다면 catch 영역에서 예외를 받는다. 예외 영역에서는 처리하려는 예외의 타입을 정할 수 있다. 이 과제에서는 catch 영역에서 (const std::exception &)를 받는다.

ex00 - Bureaucrat

Bureaucrat라는 클래스를 정의하고 grade 값이 범위를 벗어난다면 예외가 발생하도록 구현한다.
생성자에서는 name 문자열과 grade 정수를 받고, grade가 1보다 작거나 150보다 크다면 예외가 발생된다.
예외가 발생되는 시점은 생성되는 시점과 increment, decrement 멤버함수를 호출하는 시점이다. 이때마다 grade 값을 확인하고 std::exception 클래스를 상속받은 객체를 throw한다.

ex01 - Form

Form 클래스는 이전에 구현한 Bureaucrat와 비슷하게 구현된다.
생성되는 시점에 받는 sign_grade와 exec_grade를 범위에서 벗어나지 않는지 확인한다.
구현되어야 하는 멤버함수는 beSigned()이다. 이 함수는 Bureaucrat를 받아서 sign 자격을 확인한다. 인자의 grade와 자신의 sign_grade를 비교하여 sign_grade가 더 작다면 sign 할 수 없다는 예외를 발생시킨다.
추가로 이미 sign 되었음에도 시도하면 예외를 발생시키도록 구현하였다.

ex02 - Child forms

Form 클래스를 상속받는 자식 클래스를 구현한다.
자식 클래스들은 정해진 sign_grade와 exec_grade로 생성되고 execute를 override 해야한다.
Form 클래스에 execute 함수를 순수 가상 함수로 선언하여 더이상 객체로 생성될 수 없다.
자식 클래스들의 execute 함수를 각자 주어진 동작을 실행하기 전에 grade를 확인한다.
인자로 들어오는 Bureaucrat의 grade를 exec_grade와 비교하여 낮은 grade라면 예외를 발생시킨다.

cpp06 - Type casting

c에서는 형변환을 가차없이 진행했지만 c++에서는 형변환을 위한 연산자를 제공하여 조금더 안정적인 형변환을 할 수 있도록 한다.

  • static_cast : 기본 자료형 간의 형변환에 사용된다. 컴파일 단계에서 형변환을 검사하고 에러를 발생시킨다.
  • dynamic_cast : 상속 관계에서 안정적으로 형변환을 처리한다. 런타임 단계에서 형변환 검사를 진행한다.
  • reinterpret_cast : 포인터/참조의 형변환 연산자이다. 변환을 강제하기 때문에 안전하지 않다.
  • const_cast : const를 제거하기 위해 사용된다.

ex00 - Conversion of scalar types

생성자에서 input을 double로 변환하여 저장한다.
double을 다른 타입으로 타입 캐스팅하여 사용한다.
가장 넓은 범위를 표현할 수 있는 double로 변환한다.

input string을 double로 변환하기 위한 방법을 찾아야 한다.
stod()는 c++11 함수이기 때문에 사용할 수 없다.
c++98에서 사용할 수 있는 strtod() 함수를 사용하여 변환한다.

1
2
3
4
5
6
double  strtod(const char* str, char** str_end);

// str - null로 끝나는 변환될 문자열 포인터
// str_end - 숫자 문자열이 변환되고 남은 문자열의 첫번째 문자 포인터
// 성공시 - str의 내용에 해당되는 double 값
// 실패시 - 변환이 안되면 0이 반환되고, 범위를 초과한 경우에는 HUGE_VAL가 반환된다.

cppreference (strtod) 소수점 처리

Conversion 클래스의 생성자는 인자로 (const char *argv) 를 받는다. argv는 변환되어야 하는 문자열로써 반드시 char, int, float 또는 double 타입이 문자열로 표현되어 들어온다. 그리고 char를 제외하고 모두 10진수 표현이다.

  • char : non displayable 문자는 들어오지 않는다. 그리고 문자로 변환에서 표시할 수 없다면 적절한 메시지를 출력한다.
  • float : ( -inff, +inff, nanf ) 세가지를 모두 처리해야 한다.
  • double : ( -inf, +inf, nan ) 세가지를 모두 처리해야 한다.

인자로 들어온 리터럴을 확인하고, 실제 세가지 타입으로 변환해야 한다. 만약에 변환이 말이 안되거나 오버플로우라면 impossible 메시지를 출력한다.

float와 double이 출력되는 형식을 위해서 iomanip을 사용한다. 소수점을 1자리로 고정하려면 precision과 fixed를 사용하면 된다. precision은 출력되는 자리수를 정해주고, fixed는 정해진 자리수를 소수점에 한해서 적용되도록 한다.

ex01

객체 직렬화(Serialization)는 객체의 메모리를 연속적인 바이트로 만들고, 연속적인 바이트를 원래 객체로 복원하는 작업을 말한다. 주로 메모리에 있는 데이터를 스트림으로 보낼 때 사용한다. 스트림을 이용하면 객체를 파일에 입력이나 출력을 할 수 있으며, 네트워크에서 송수신할 수 있다.

ex02

dynamic_cast으로 상속 관계의 변환을 다룬다. dynamic_cast는 상속 관계에 있는 클래스의 형변환을 위해 사용된다. static_cast는 컴파일 시점에서 형변환이 가능한지 검사하지만 dynamic_cast는 런타임에 형변환이 가능한지 확인하게 된다. 그래서 dynamic_cast를 사용한 뒤에 정상적으로 형변환이 일어났는지 확인하는 과정이 필요하다. 형변환이 성공한 경우에는 정상적으로 변환된 주소의 값을 반환하지만 실패하는 경우는 두가지로 나누어 진다. 포인터를 변환하는 경우에 실패하면 NULL 포인터를 반환하고, 참조를 변환하는 경우에 실패하면 예외를 던진다. 변환이 성공하는 경우와 실패하는 경우를 구별하여 과제를 해결할 수 있다.

cpp07 - Template

템플릿 함수와 템플릿 클래스를 구현하는 과제이다.
템플릿은 함수나 클래스를 개별적으로 작성하지 않고, 여러 자료형으로 사용할 수 있도록 만들어 놓는 틀이다.

템플릿은 함수나 클래스를 찍어내는 형틀이다.
c++에서 함수를 정의할 때 오버로딩 특성에 의해서 같은 이름의 함수를 인자의 타입을 다르게 하여 반복적으로 정의하게 되는데 이를 템플릿이 해결해준다. 템플릿 함수/클래스를 정의하면 컴파일러가 자동으로 타입을 변환하여 작성해준다. 정의된 템플릿 함수/클래스를 호출하면 호출부의 인수의 타입을 읽어서 그에 맞는 함수/클래스를 작성한다. 즉, 모든 타입의 함수/클래스가 만들어지지 않고 호출된 타입의 함수/클래스만 만들어진다.

ex00

swap, min, max 함수를 템플릿 함수로 구현하는 과제이다. 템플릿 함수로 구현되기 때문에 아무 타입이 들어와도 사용할 수 있는 함수가 된다.

ex01

배열의 요소에 함수를 사용하는 함수인 iter를 구현한다. 인자로 배열, 배열 길이, 함수 포인터가 들어온다.

예외처리 (배열, 배열 길이, 함수 포인터)

  • 함수 포인터의 매개변수 타입이 배열 요소의 타입과 다르다면 컴파일 에러가 발생된다. 타입이 같거나 템플릿인 경우에는 컴파일된다.
  • 배열로 null이 들어오는 경우에는 컴파일 에러가 발생된다.
  • 배열 길이가 음수로 들어오는 경우에 segmentation fault가 뜬다.
  • 함수 포인터의 경우에도 null이 들어올 수 없다. 컴파일러가 타입 검사를 진행하여 컴파일 에러를 발생시켜서 컴파일되지 않는다.

배열의 길이에서 음수인 경우만 처리하면 된다. 만약에 오버플로우가 발생하는 값을 넣으면 컴파일러가 타입을 long으로 이해하고 매개변수로 받으려는 타입과 다름을 확인하고 에러를 발생시킨다.

ex02

템플릿 클래스 Array를 구현한다.

  • 인자가 없는 생성자는 빈배열을 생성
  • unsigne int n 인자를 받는 생성자는 n 요소의 배열을 만든다.
  • 복사 생성자와 할당 연산자를 구현하는 경우에는 원본 배열이나 복사본을 수정해도 다른 배열에 영향이 없어야 한다.
  • new[] 연산자를 사용할 때 반드시 메모리를 할당해야 한다. 메모리 사전 할당은 금지된다. 프로그램은 할당되지 않은 메모리에 접근해서는 안된다.
  • [] 연산자로 요소에 접근할 수 있어야 한다.
  • [] 연산자로 범위를 벗어난 요소에 접근을 시도하는 경우에는 예외를 던진다.
  • size() 멤버 함수는 배열의 요소 개수를 반환한다. 이 멤버 함수는 인자가 없으며 반드시 현재 인스턴스를 수정하지 않아야 한다.

const, non_const

  • const와 non const의 차이를 어디에서 알 수 있을까?
  • 객체를 const로 선언한 경우에는 non-const 멤버함수를 사용할 수 없다. 변경하지 않는다고 선언된 객체인데 객체 내용을 변경하는 함수를 호출하려 하기 때문에 컴파일러가 에러를 발생시킨다.
  • const 값을 반환하는 멤버 함수를 구현해야 하는가?
  • 구현해야하는 함수 중에는 const를 반환해야하는 함수가 없다.
  • [] 연산자가 반환하는 값은 배열 요소의 참조 값이다. 이는 변경될 가능성이 높기 때문에 const를 붙이지 않아도 된다.

  • const Array에서 [] 연산자 키워드로 요소에 접근할 수 있다. 그러나 연산자에서 반환된 요소는 변경할 수 없어야 한다. 그래야 Array의 const가 유지된다.
  • [] 연산자가 const 참조가 아닌 그냥 참조를 반환한다면 값을 변경할 수 있다. 그러므로 const 변수의 [] 연산자가 반환하는 값은 const이어야 한다.

객체가 const인 경우에는 non_const 멤버 함수를 사용할 수 없고, 멤버 함수가 객체의 요소를 참조로 반환하는 경우에는 const 참조로 반환하여 객체의 요소의 변경을 방지한다.

default constructor

과제에서는 빈배열을 반환하라고 나와있다. 그렇다면 new T[0]로 생성해야하는 걸까 아니면 NULL을 배열에 놓아야 하는걸까?
두가지 방법이 모두 상관없다고 보인다. private 영역에 선언되어 외부에서는 접근할 수 없기에 다음의 멤버 함수에서 문제가 없으면 된다. [] 연산자에서 요소에 접근할 때 예외를 발생시키고, size()에서 0이 반환되야 한다. 복사 생성자 또는 할당 연산자 에서 객체가 복사될 때 메모리를 해제된다면 문제없다.

new 연산자

new 연산자는 메모리 공간 할당, 생성자 호출, 자료형에 맞게 반환된 주소 값이 형 변환한다. 그러므로 c처럼 반환되는 주소값의 형변환을 하지 않아도 된다.

1
2
void* operator new      (std::size_t count);
void* operator new[]    (std::size_t count);
  • new 연산자 오버로딩

new 연산자를 오버로딩하려면 메모리 공간의 할당을 구현하면 된다. new 연산자를 사용하면 먼저 필요한 메모리 공간을 계산한다. 이때 operator new 함수를 호출하며 인자로 크기 값을 전달한다. 그래서 다음과 같이 operator new 함수를 정의하면 된다.

1
2
3
4
5
void* operator new (size_t size)
{
    void *ptr = new char[size];
    return ptr;
}
  • delete 연산자 오버로딩

cpp08 - Container, Algorithm

ex00

easyfind라는 템플릿 함수를 구현하며 T라는 타입을 받는다.
두 매개변수를 받는데 첫번째는 T 타입이고, 두번째는 int 타입이다.
T 타입을 int container라고 가정하면, 두번째 매개변수를 int container에서 찾는다.
만약에 없으면 예외를 던지거나 에러 값을 반환한다.
Associative container는 제외한다. 표준 컨테이너가 어떻게 동작하는지 분석하면 방법을 알 수 있다.

  • standard container (vector, deque, list)
  • vector : 크기가 변할 수 있는 배열을 나타내는 시퀀스 컨테이너이다.
    • 배열과 달리 크기가 동적으로 변경될 수 있으며 저장공간은 컨테이너에서 자동으로 처리된다.
    • 내부적으로 vector는 동적으로 할당된 배열을 사용하여 요소를 저장한다.
    • 새 요소가 삽입될 때 크기를 늘려야 한다면 새 배열을 할당하고 모든 요소를 옮긴다.

    • 컨테이너 속성 : Sequence, Dynamic array, Allocator-aware
    • Sequence : 요소는 엄격한 선형 시퀀스로 정렬된다. 개별 요소에 해당 위치로 접근된다.
    • Dynamic array : 포인터로 모든 요소에 직접 접근할 수 있으며, 끝에 추가/제거를 빠르게 할 수 있다.
    • Allocator-aware : 저장공간을 동적으로 처리한다.
  • deque(double ended queue) : 양방향 큐는 양쪽 끝에서 확장하거나 축소할 수 있는 동적 크기의 시퀀스 컨테이너이다.
    • 일반적으로 동적 배열의 일부 형태로 다양한 방식으로 구현될 수 있다.
    • 개별 요소는 임의 접근 반복자를 통해 직접 접근할 수 있다.
    • 필요에 따라서 컨테이너를 확장 및 축소하여 저장 공간을 자동으로 처리할 수 있다.
    • vector와 유사하지만 시작 부분에도 요소를 효율적으로 삽입/삭제한다.
    • 모든 요소가 인접한 저장 위치에 저장된다고 보장하지 않으므로 다른 요소에 대한 포인터를 오프셋하여 접근하면 정의되지 않은 동작이 발생한다.
    • 시작과 끝이 아닌 위치에 요소를 삽입/제거하는 작업의 경우에 list보다 성능이 저하되고 반복자와 참조의 일관성이 떨어진다.
  • list : 시퀀스 내에서 어느 곳에나 삽입/제거를 일정한 시간에 수행하고 양방향으로 반복할 수 있는 시퀀스 컨테이너이다.
    • list는 이중 연결 리스트로 구현된다.
    • 이중 연결 리스트는 서로 관련 없는 저장 위치에 포람된 각 요소를 저장할 수 있다.
    • list는 일반적으로 반복자가 이미 확보된 컨테이너 내의 모든 위치에서 요소의 삽입/추출/이동에서 좋은 성능을 보이기 때문에 이러한 속성을 사용하는 알고리즘에서 수행된다.
    • 주요 단점은 위치별로 요소에 직접 접근할 수 없다. 시작이나 끝같은 위치에서 해당 위치까지 반복해야 하므로 선형 시간이 걸린다.
  • 여러 컨테이너에서 어느 요소를 찾는다.
  • 컨테이너에 따라서 요소를 찾는 방법은 다르다.
  • 타입을 분기할 수 있는 방법을 찾아야 한다.
  • 템플릿 특수화를 사용하여 컨테이너를 구분하여 취급할 수 있다.

ex01

Span이라는 정수를 최대 N개 저장할 수 있는 클래스를 구현한다.
N은 unsigned int 변수이고, 오직 생성자로만 전달된다.

addNumber()는 하나의 수를 Span에 추가한다.
이미 존재하는 요소를 넣으려고 시도하면 예외를 발생시킨다.

shortestSpan()과 longestSpan() 함수는 가장 가까운 거리과 가장 긴 거리를 찾는다.
만약에 아무 숫자도 저장되지 않았거나 하나가 있으면 거리를 찾을 수 없다. 그러므로 예외를 던진다.
Span을 테스트해라 많을 수록 좋다.

마지막으로 다양한 반복자를 사용하여 Span을 채워라.
매번 addNumber()를 호출하는 번거로움을 피할 수 있다.
한번의 호출로 Span에 많은 숫자를 넣는 멤버 함수를 구현해라.

과제에 적합한 컨테이너를 골라야 한다.

  • 이미 존재하는 요소를 넣으려하면 예외를 발생
  • Span의 크기를 최대로 키워야 하므로 요소를 저장하며

  • 요소를 저장할때 이전의 값과의 거리를 저장한다.
    • 과제의 목적이 이것이라면 컨테이너가 필요없다.
    • 새로운 수가 들어올 때마다 이전과 거리를 계산해서 min 또는 max 변수에 저장하는게 더 쉬운 방법이기 때문이다.
    • 그러니 과제의 목적은
  • 계산된 값을 컨테이너에 저장하면 자동으로 정렬되어야 한다.
  • 연관 컨테이너가 과제를 구현하는데 적합해 보인다.
  • 구현되어야하는 두 멤버함수가 span 값을 반환하므로 요소가 그대로 있지 않아도 된다.

연관 컨테이너 (associative container)

set

노드 기반 컨테이너로 균형 이진트리로 구현되어있다.
key라는 원소들의 집합으로 이루어져 있으며 key 값은 중복이 허용이 되지 않는다.
원소가 insert로 삽입되면 자동으로 정렬된다.
default 정렬 기준은 오름차순이다.

set의 멤버 함수

  • set.begin() : 첫번째 원소를 가르키는 반복자를 반환한다.
  • set.end() : 마지막 원소를 가리키는 반복자를 반환한다.
  • set.clear() : 모든 원소를 제거한다.
  • set.empty() : 비어있는지 확인한다.
  • set.insert(k)
    • 원소 k를 삽입하며 자동으로 정렬된 위치에 들어간다.
    • 삽입의 성공 여부는 리턴값으로 나오게 된다. ( pair<iterator, bool> )
    • pair<iterator, bool>에서 첫번째는 삽입한 원소를 가리키고, 두번째는 성공과 실패를 나타낸다.
  • set.size() : 사이즈를 반환한다.

클래스 내에 set 컨테이너 객체를 가지고 있다. set은 요소를 삽입하는 경우에 자동으로 정렬하고, 이미 존재하는 요소와 똑같은 요소는 삽입되지 않는다. 그러므로 이 과제를 구현하기에 적절한 컨테이너이다.

과제는 addNumber() 멤버 함수를 구현하여 수를 추가한다. 이 함수는 하나의 숫자만 넣을 수 있으므로 많은 숫자를 추가한 후에 테스트하려면 번거롭게 addNumber()를 여러번 호출해야 한다. 그러므로 여러 수를 한번에 추가하는 방법이 필요하다. addRange() 멤버 함수는 컨테이너를 인자로 받아서 요소를 추가한다.

addRange() 멤버 함수의 인자로 들어올 수 있는 컨테이너는 다양하다. 그러므로 템플릿 함수로 작성하고 컨테이너의 유효성 검사를 진행해야한다. 먼저, 요소가 추가될 컨테이너에 공간이 충분히 있는지 확인하고 중복된 요소가 있는지 확인한다. 유효성 검사를 마친후에는 algorithm의 copy함수로 컨테이너에 있는 요소를 복사해주며 함수가 종료된다.

예외 상황은 세가지이다. 이미 존재하는 요소를 추가하려는 경우, 공간이 가득찬 상황에서 addNumber()를 호출한 경우, 공간이 비어있거나 요소가 하나인 상황에서 shortestSpan()이나 longestSpan()을 호출하는 경우이다.

ex02

iterator를 사용하는 stack 컨테이너 클래스를 구현하는 과제이다.
MutantStack 클래스를 정의한다.
std::stack의 관점에서 구현된다. 모든 멤버 함수를 제공하고, 추가로 반복자를 제공한다.

MutantStack을 실행하고, 다른 컨테이너로 대체되어도 같아야 한다.
다른 컨테이너를 테스트할 때 push()는 push_back()이 될 수 있다.

LIFO stack

stack은 컨테이너 어댑터 유형으로 컨테이너의 한쪽 끝에서만 요소가 삽입/추출되는 LIFO에서 작동하도록 설계되어있다.

  • 컨테이너 어댑터 (container adapter)
    • 기존 컨테이너의 인터페이스를 제한하여 만든 기능이 제한되거나 변형된 컨테이너를 의미한다.
    • 컨테이너의 기초가 되는 클래스의 인터페이스를 제한하고, 특정 형태의 동작만을 수행하도록 한다. 반복자를 지원하지 않는다.

stack 컨테이너의 디폴트 컨테이는 deque이다.

1
2
3
explicit stack (const container_type& ctnr = container_type());

// ctnr : 컨테이너 객체

아래와 같이 스택 생성시 list<int>를 추가하여 스택 컨테이너의 자료형을 deque에서 list로 변환가능하다. stack 컨테이너 어댑터는 vector, list, deque에 적용할 수 있다. 즉 모두 stack이 될 수 있다.

1
stack<int, list<int>>   s;

새로운 클래스를 정의하는 경우에 stack이 가진 멤버 함수를 모두 구현한다면 stack을 적용할 수 있다. stack의 기능이 제한된 이유는 여러 컨테이너에 적용할 수 있게 하기 위함이다. 이러한 이유 때문에 stack은 반복자를 사용할 수 없다. 과제에서는 반복자를 사용할 수 있는 stack을 구현하는 것이 목표이다.

멤버 함수

  • empty() : 스택이 비어있으면 true, 아니면 false를 반환
  • size() : 스택 요소의 총 개수를 반환
  • top() : 스택의 가장 상단에 있는 요소에 대한 참조를 반환
  • push() : 스택의 가장 상단에 요소를 삽입
  • pop() : 스택의 가장 상단에 있는 요소를 삭제

stack의 멤버 함수를 모두 구현한다. stack에서 iterator를 사용할 수 있도록 해야한다.

iterator

컨테이너의 원소들을 순회할 수 있는 객체이다. 일종의 포인터로서 특정 위치를 가리킨다.
포인터처럼 반복자가 동작하기 위해서 다음과 같은 조건을 만족해야 한다.

  • 가리키는 요소의 값에 접근할 수 있어야 한다. 참조 연산자 정의 (*)
  • 반복자는 대입, 비교 연산이 가능해야 한다.

사실은 굉장히 간단한 문제이다. stack에서 사용하지 않는 멤버 함수를 사용할 수 있는 래핑 클래스를 구현하면 된다.

MutantStack 클래스를 정의하는 과제이다. 이번 과제에서는 iterator를 사용하는 stack 컨테이너를 정의해야 한다. 기존의 stack 컨테이너는 iterator를 사용하지 않는다. stack은 어댑터 컨테이너로써 다른 컨테이너의 상속을 받아서 사용하는 컨테이너이다. 그런데 stack은 자료 구조에 알맞게 다른 컨테이너 멤버 함수 중에서 필요한 함수만 골라서 사용하고 있다. stack에서 사용할 수 있는 함수는 empty, size, top, push, emplace, pop, swap이다. 이는 단순히 인터페이스를 제공하지 않았을 뿐이므로 stack을 상속받아서 iterator에 관련된 함수를 추가하면 된다. 컨테이너에서 사용하는 iterator 함수는 begin, end, rbegin, rend이다.