#include <string>
#include <vector>
#include <utility>
#include <functional>
namespace cyt {
class UI {
private:
// static std::vector<std::pair<std::string, decltype(UI::quit)*>> actions;
// static std::vector<std::pair<std::string, std::function<void()>>> actions;
static std::vector<std::pair<std::string, void(*)()>> actions;
UI() {}
public:
static void add_tag();
static void print_all_tags();
static void add_time_record();
static void print_all_time_records();
static void print_action_list();
static void quit();
static const int get_action();
static const bool do_action(int i);
static void run();
};
}
UI.hh 파일
위 코드는 함수의 이름과 함수 포인터를 저장하여, 사용자로부터 입력을 받아 해당 함수를 호출하기 위한 코드이다.
맨 위 주석은 decltype을 이용하여 함수 포인터 타입을 정의하는데, 이 코드를 사용하기 위해선 UI::quit이 먼저 선언되어있어야 하므로 순서를 바꿔주어야 한다.
그 다음 주석은 사실 C++에서 가장 권장되는 코드라고 볼 수 있다.
functional header에 포함된 std::function을 이용한다.
std::function은 C++의 모든 callable object - 5가지: 함수, 함수포인터, lambda, std::bind, function object (class with overloaded call operator) 전부 캡쳐할 수 있다.
(참조 stackoverflow.com/questions/25848690/should-i-use-stdfunction-or-a-function-pointer-in-c)
private static member인 actions는 std::vector이고, pair<std::string, void(*)()>를 template parameter로 갖는다.
이때 UI 클래스의 멤버는 모두 static이고 constructor가 private이기 때문에 객체 생성 없이 사용된다.
주목할 점은 함수 포인터의 대상을 static member function으로 상정한 것인데,
static member function에 대한 pointer의 경우 non-member function의 pointer와 표기법이 동일하다.
만약 non-static member function을 사용한다면 void(UI::*)() 라고 특정 클래스의 멤버 함수임을 명시해야 한다.
(참조 https://isocpp.org/wiki/faq/pointers-to-members#fnptr-vs-memfnptr-types)
위의 actions는 declaretion이지만 definition으로 간주되지 않는다.
static data member는 해당 클래스의 인스턴스에 종속되지 않기 때문에 인스턴스가 생성될 때 초기화되지 않는다.
따라서 static data member를 정의하기 위해서는 반드시 클래스 외부에서 따로 명시적으로 정의를 해주어야 한다.
#include "UI.hh"
#include "TimeRecordController.hh"
#include <iostream>
#include <sstream>
using namespace cyt;
decltype(UI::actions) UI::actions;
/*
... 중략 ...
*/
void UI::run()
{
TimeRecordController controller();
cyt::UI::actions.push_back(std::make_pair("add a time record", cyt::UI::add_time_record));
cyt::UI::actions.push_back(std::make_pair("print all time records", cyt::UI::print_all_time_records));
cyt::UI::actions.push_back(std::make_pair("quit", cyt::UI::quit));
/* .. 생략 .. */
}
#include "UI.hh"
int main()
{
cyt::UI::run();
return 0;
}
UI.cc 파일과 main 함수
클래스가 선언된 헤더 파일 외부의 .cc 파일에서 초반부에 UI::actions를 작성했다.
위에서 보았듯이 actions는 클래스 외부에서 사용할 수 없도록 private으로 선언하였다.
따라서 actions에 접근은 오직 클래스의 멤버만 가능한데,
반드시 클래스 외부에서 정의해서 초기화한 후에야만 actions를 사용할 수 있다.
타입과 이름만 명시해주면 actions는 std::vector이기 때문에 내용물이 없는 상태로 default initiallized된다.
(C++에서 초기화 initialization, 할당 assignment, 이 두가지는 서로 다르다!! 굉장히 critical한 point이다.)
이후에 run 함수가 실행될 때 실질적으로 actions에 데이터가 추가된다.
program이 시작되면서 위의 statement를 컴파일러가 보았을 때 actions가 초기화되었기 때문에
같은 UI 클래스의 멤버함수인 run이 actions를 사용할 수 있는 것이다.
참고자료: C++ Primer 5th 247~250p, 300~302p
'컴퓨터 언어 > C++' 카테고리의 다른 글
template keyword: class vs typename (0) | 2020.07.25 |
---|---|
Primitive Built-In Types (0) | 2020.07.23 |
Copy vs Move Constructor/Assignment (0) | 2020.07.15 |