CPP Module 01 (2)

“CPP Module 01에 대하여”

Memory allocation, pointers to members, references, switch statement

C++의 메모리 할당, 포인터와 멤버들, 참조들, switch문 … C++에서도 메모리 관리는 계속된다.

ex04: Sed is for losers

  • 42서울을 했다면 sed(Stream Editor)를 모르진 않을거다. 난 잘 모를지도…



  • ??? : Sed가 “패배자들을 위한 것” 이라고 말하는 것 부적절해… 42 서울은 Sed에게 사과해야…

fstream

  • C++의 파일 입출력을 맛보는 시간이다. module 00에서 입출력 라이브러리에 대해 이야기 했던 기억이 난다.

출처, C++ IOstream (입출력) 라이브러리


  • 큰그림을 그리자면, 우리가 지난 모듈에서 했던 cin이니 getline이니, goodbit니 먼 비트니 했던 이야기들과, 여기 fstream 이야기는 그렇게 멀리 떨어진 이야기가 아니다. C++의 모든 입출력은 위 그림에 그려진 구조에 의해 관리되고 있다(상세한 명세는 링크를 공부해보자).

  • 이번 과제는 ifstream으로 원본 파일을 읽어서 조작을 가한 후, ofstream으로 그 조작한 복사본 파일을 쓰는 것이다. 살펴볼 것은 크게 두 가지로,

    (1) fstream 사용하여 파일 열기

    (2) std::replace 쓰지 않고 문자열 대체하기

    가 되겠다.

fstream 사용하여 파일 열기

    std::ifstream ifs;
    std::ofstream ofs;

    // void open(const char* __s, ios_base::openmode __mode)
    ifs.open(argv[1]); // ifs.open(argv[1], std::ifstream::in) 
    if (ifs.fail())
        // ... implementation
    ifs.close();

    ofs.open(argv[1] + ".replace"); // ofs.open( ..., std::ofstream::out)
    if (ofs.fail())
        // ... implementation
    ofs.close();
  • 대략 이런 식으로 열고 닫고 할 것이다.

과제에서 제한하는 C 스타일 파일 입출력 함수는 fopen, fclose 되시겠다. open은 POSIX 표준 라이브러리에서 제공하는 파일 입출력 함수이며 C와 C++ 모두에서 사용할 수 있다. open()은 C와 C++이 제공하는, 운영 체제와 상호 작용하는 대표적인 시스템 콜(System Call) 함수이다.

  • 내가 C++를 처음 배우고 클래스에 대한 개념이 거의 없었을 땐 std::ifstream ifs라는 라인 자체가 신기했던 기억이 난다. 내가 만든 조잡한 클래스를 선언하는 것과, 파일을 열기 위해서 클래스를 선언하는 것이 동일한 라인에 있다는 것이 놀랍다는 생각이 컸던 것 같다. 당연히 ifstream이나 ofstream도 클래스기 때문에(물론 아주 잘 설계된), 기능을 가져다 쓰려면 선언을 하고, 멤버 함수들을 불러야 한다. 당연히 open은 ifstream, ofstream의 멤버 함수이다. 레퍼런스를 참고해보자.
  • std::ifstrean::open, std::ofstream::open

  • 열었으면 닫아주도록 하자! 제출한 뒤에 허겁지겁 닫지 말고

std::replace 쓰지 않고 문자열 대체하기

  • std::replace를 쓸 수 없기 때문에, 파일을 읽으면서 하나하나씩 찾아서 바꿔줘야 한다.

  • 파일로부터 읽고(std::getline), 바꿔야 할 문자열을 찾은 후(std::string::find), 문자열 만큼을 지우고(std::string::erase), 그 자리에 새로운 대상 문자열을 채워넣으면(std::string::insert) 된다. 이렇게 EOF까지 진행하면 된다.

  • C++가 문자열 처리 부분에 있어 특장점이 있는 언어는 아니라고 하지만, C 하다가 온 우리에게는 혁명이 아닐 수가 없다. std::string의 멤버 함수들에 대해 잘 알고 있다면 아주 어려운 처리는 아니라고 생각하는데, 나는 얼른 생각해내질 못해서 여러모로 열심히 찾아보았었다.

  • 참고, C++ Reference > string, 왠만한 string 멤버 함수들은 다 공부해볼 수 있다.

My Code

    while (1)
	{
		std::getline(ifs, line);			
		size_t pos = 0;
		while (1)
		{
			pos = line.find(s1, pos);
			if (pos == std::string::npos)
				break;
			line.erase(pos, s1.length());
			line.insert(pos, s2);
			pos += s2.length();
		}
		ofs << line;
		if (ifs.eof())
			break ;
		ofs << std::endl; // 개행 첨가
	}
  • 많이 짜보질 않아서 그런 건지, 여기저기서 참고하면서 어렵게 만들었던 코드다. getline은 개행을 구분자로 쓰면서 버퍼에 개행을 남기지 않기 때문에 문자열을 치환한 이후 ofstream에 써 넣을때 개행을 넣어 주어야 한다.

ex05: Harl 2.0

Message Logging Functions

  • 클래스 Harl은 크게 4가지 Logging Functions을 가지며, 어떤 인자가 들어오느냐에 따라(“DEBUG”, “INFO”, “WARNING”, “ERROR”) 다른 메세지를 출력한다.

  • 메세지 로깅 함수(logging functions)는 주로 프로그램 또는 소프트웨어 애플리케이션에서 발생하는 이벤트, 상태 정보, 오류 메시지 및 디버깅 정보를 기록하고 문제 해결 및 모니터링에 사용하는 기능으로, 이 함수들은 주로 소프트웨어 개발, 디버깅, 성능 모니터링, 보안 감시 등 다양한 목적으로 활용된다고 한다.

  • 참고, IBM, 메세지 로깅 함수.

Harl has to complain without using a forest of if/else if/else

  • if/else문 자체를 못쓰게 한 것은 아닌데… if/else문을 사용하면서 함수 4개를 로깅하려면 중첩해서 사용하는 것이 불가피하다. 물론 어떻게 몸을 비틀면 가능할지도 모르지만, 마음 편하게 switch문을 사용하는 것이 좋을 것 같다. 다음 과제에서 살펴보겠지만 switch문은 메세지 로깅 함수와 비슷한 작동 방식을 가지고 있기도 하다.

멤버 함수 포인터 배열

void    Harl::debug 
{ 
    std::cout << "DEBUG" << '\n';
}

void    Harl::info 
{
    std::cout << "INFO" << '\n';
}

void    Harl::complain()
{
    void (Harl::*f[2])(void) = { // f가 이름인데, 이름 앞에 Harl:: 명시
        &Harl::debug,
        &Harl::info
    }; // debug, info 앞에 Harl:: 명시하여 멤버 함수임을 드러냄
   
    std::cout << (this->*f[0])() << ' ' << (this->*f[1])() << std::endl;
    // 실제로 사용하려면, if문이나 switch 문의 분기에 (this->*f[n])() 처럼 명시
}
❯ ./a.out
DEBUG INFO
  • C에서도 골치를 썩혔던 함수 포인터가, C++ 클래스와 만나면서 더욱 신나는 모양으로 돌아왔다. 마찬가지로 class의 멤버 함수들도 함수 포인터로 사용이 가능하다. class 내부의 멤버 함수들은 객체를 통해서만 출력이 가능하고, 함수 포인터의 선언 또한 해당 클래스의 범위 내에 있는 함수 포인터라는 것을 명시해야 한다.
    void (*func)(); // C 함수 포인터
  • C의 함수 포인터가 기억날 것이다. 기억 안나면 여기.

  • C와 다르게 C++ 함수 포인터는 이름 앞에 클래스 명을 명시하고, 함수를 매칭시킬 때도 함수가 객체 내에 속해있다는 것을 명시해주는 것이 포인트라고 할 수 있겠다.

ex06: Harl filter

메세지 로깅 함수와 Switch Case Statement

  • IBM 문서에 따르면, 메세지 로깅 함수에는 다섯 가지 레벨이 있으며 순서대로 DEBUG, INFO, WARNING, ERROR 및 FATAL로 이루어져 있다(우리 과제에서는 FATAL은 사용하지 않는 것 같다). 그리고 예를 들어 로그 레벨을 WARNING으로 설정하면 WARNING, ERROR 및 FATAL 메세지만 로깅되지만 로깅을 ERROR로 설정하면 ERROR 및 FATAL 메세지가 로깅된다. 과제를 진행할 때는 눈치채지 못했는데 메세지 로깅 함수의 이 작동 방식은 Switch Case Statement의 Fall-Through와 기능 방식이 매우 유사하다. 과제의 밑단의 You must use, and maybe discover, the switch statement in this exercise.라는 문구는 바로 이 점을 시사한다고 볼 수 있겠다.

  • Switch문에 대한 상세한 설명은 C++ 06.02 - switch 문 (switch statement) 참고.

Switch문의 Fall-Through

  • subject의 예시 동작이 바로 switch문의 Fall-Through라고 볼 수 있다.
$> ./harlFilter "WARNING"
[ WARNING ]
I think I deserve to have some extra bacon for free.
I've been coming for years whereas you started working here since last month.
[ ERROR ]
This is unacceptable, I want to speak to the manager now.
$> ./harlFilter "I am not sure how tired I am today..."
[ Probably complaining about insignificant problems ]
  • break 등으로 switch 문을 탈출시켜주지 않으면 계속해서 다음 case를 실행하는 fall-through를 이용하면, 위의 메세지 로깅을 쉽게 구현해낼 수 있다. case 문에 DEBUG, INFO, WARNING, ERROR에 대한 분기를 작성하고 switch 문을 탈출시키지 않으면 되겠다. 다만 마지막 단계인 ERROR나, default case의 경우 설계에 따라 탈출시켜줘야 할 수도 있다.