<Programming: Principles and Practice Using C++, 2nd edition>
위와 같은 원서로 C++ 강좌를 듣고 있는데 std_lib_facilities.h 라는 난생 처음 보는 헤더 파일로 프로그래밍을 배우고 있습니다. 사실 수업만 따라가면 알 수 있는 내용이였으나 이 파일을 아예 처음 보셨다거나 수업에서 접하게 되었는데 설명없이 넘어가서 뭔지 모르겠다는 분들을 위해 이 헤더 파일이 무엇인지 간단하게 설명해드립니다.
/*
std_lib_facilities.h
*/
/*
simple "Programming: Principles and Practice using C++ (second edition)" course header to
be used for the first few weeks.
It provides the most common standard headers (in the global namespace)
and minimal exception/error support.
Students: please don't try to understand the details of headers just yet.
All will be explained. This header is primarily used so that you don't have
to understand every concept all at once.
By Chapter 10, you don't need this file and after Chapter 21, you'll understand it
Revised April 25, 2010: simple_error() added
Revised November 25 2013: remove support for pre-C++11 compilers, use C++11: <chrono>
Revised November 28 2013: add a few container algorithms
Revised June 8 2014: added #ifndef to workaround Microsoft C++11 weakness
*/
#ifndef H112
#define H112 251113L
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdlib>
#include <string>
#include <list>
#include <forward_list>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <array>
#include <regex>
#include <random>
#include <stdexcept>
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
typedef long Unicode;
//------------------------------------------------------------------------------
using namespace std;
template<class T> string to_string(const T& t)
{
ostringstream os;
os << t;
return os.str();
}
struct Range_error : out_of_range { // enhanced vector range error reporting
int index;
Range_error(int i) :out_of_range("Range error: "+to_string(i)), index(i) { }
};
// trivially range-checked vector (no iterator checking):
template< class T> struct Vector : public std::vector<T> {
using size_type = typename std::vector<T>::size_type;
#ifdef _MSC_VER
// microsoft doesn't yet support C++11 inheriting constructors
Vector() { }
explicit Vector(size_type n) :std::vector<T>(n) {}
Vector(size_type n, const T& v) :std::vector<T>(n,v) {}
template <class I>
Vector(I first, I last) : std::vector<T>(first, last) {}
Vector(initializer_list<T> list) : std::vector<T>(list) {}
#else
using std::vector<T>::vector; // inheriting constructor
#endif
T& operator[](unsigned int i) // rather than return at(i);
{
if (i<0||this->size()<=i) throw Range_error(i);
return std::vector<T>::operator[](i);
}
const T& operator[](unsigned int i) const
{
if (i<0||this->size()<=i) throw Range_error(i);
return std::vector<T>::operator[](i);
}
};
// disgusting macro hack to get a range checked vector:
#define vector Vector
// trivially range-checked string (no iterator checking):
struct String : std::string {
using size_type = std::string::size_type;
// using string::string;
char& operator[](unsigned int i) // rather than return at(i);
{
if (i<0||size()<=i) throw Range_error(i);
return std::string::operator[](i);
}
const char& operator[](unsigned int i) const
{
if (i<0||size()<=i) throw Range_error(i);
return std::string::operator[](i);
}
};
namespace std {
template<> struct hash<String>
{
size_t operator()(const String& s) const
{
return hash<std::string>()(s);
}
};
} // of namespace std
struct Exit : runtime_error {
Exit(): runtime_error("Exit") {}
};
// error() simply disguises throws:
inline void error(const string& s)
{
throw runtime_error(s);
}
inline void error(const string& s, const string& s2)
{
error(s+s2);
}
inline void error(const string& s, int i)
{
ostringstream os;
os << s <<": " << i;
error(os.str());
}
template<class T> char* as_bytes(T& i) // needed for binary I/O
{
void* addr = &i; // get the address of the first byte
// of memory used to store the object
return static_cast<char*>(addr); // treat that memory as bytes
}
inline void keep_window_open()
{
cin.clear();
cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
cout << "Please enter a character to exit\n";
char ch;
cin >> ch;
return;
}
inline void keep_window_open(string s)
{
if (s == "") {
keep_window_open();
return;
}
cin.clear();
cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
for (;;) {
cout << "Please enter " << s << " to exit\n";
string ss;
while (cin >> ss && ss!=s)
cout << "Please enter " << s << " to exit\n";
return;
}
}
// error function to be used (only) until error() is introduced in Chapter 5:
inline void simple_error(string s) // write ``error: s and exit program
{
cerr << "error: " << s << '\n';
keep_window_open(); // for some Windows environments
exit(1);
}
// make std::min() and std::max() accessible on systems with antisocial macros:
#undef min
#undef max
// run-time checked narrowing cast (type conversion). See ???.
template<class R, class A> R narrow_cast(const A& a)
{
R r = R(a);
if (A(r)!=a) error(string("info loss"));
return r;
}
// random number generators. See 24.7.
inline int randint(int min, int max) { static default_random_engine ran; return uniform_int_distribution<>{min, max}(ran); }
inline int randint(int max) { return randint(0, max); }
//inline double sqrt(int x) { return sqrt(double(x)); } // to match C++0x
// container algorithms. See 21.9.
template<typename C>
using Value_type = typename C::value_type;
template<typename C>
using Iterator = typename C::iterator;
template<typename C>
// requires Container<C>()
void sort(C& c)
{
std::sort(c.begin(), c.end());
}
template<typename C, typename Pred>
// requires Container<C>() && Binary_Predicate<Value_type<C>>()
void sort(C& c, Pred p)
{
std::sort(c.begin(), c.end(), p);
}
template<typename C, typename Val>
// requires Container<C>() && Equality_comparable<C,Val>()
Iterator<C> find(C& c, Val v)
{
return std::find(c.begin(), c.end(), v);
}
template<typename C, typename Pred>
// requires Container<C>() && Predicate<Pred,Value_type<C>>()
Iterator<C> find_if(C& c, Pred p)
{
return std::find_if(c.begin(), c.end(), p);
}
#endif //H112
std_lib_facilities.h 의 전문입니다. 그렇게 내용이 길진 않습니다.
정확히는 위 책의 저자가 C++ 개발자인데, 책의 내용 자체가 C++ 이라는 언어의 문법을 처음부터 Step by Step 으로 배우는게 아니라 C++ 의 문법은 적당히 이해하고 언어를 사용해서 프로그램 자체를 잘 짜는 방법에 대해 치중하여 배웁니다.
* 아마 C++ 라는 언어 내용 자체가 워낙 방대해서 모든 문법을 습득하는게 사실상 불가능하므로 이런 방법을 택한 듯 합니다.
그래서 이 헤더파일 역시 저자가 제공하는 것으로 알고있는데 std_lib_facilities.h 헤더파일을 이용하면 프로그래밍에 필요한 여러 #include 전처리기 선언문, 유용한 함수들이 추가된 상태로 코딩을 시작할 수 있습니다.
보시면 iostream, vector 같은 것을 #include로 추가해주고 있고 심지어 일종의 금기인 using namespace std; 선언문까지 들어가 있네요. 책에서 네임스페이스에 관한 내용은 나중에 설명하기 때문에 std를 그냥 빼고 설명하는 방식을 택한거 같습니다.
이외에도 sort(벡터 or 배열); 라고 입력해주면 알파벳 순으로 정렬해주고, error("에러메세지") 함수를 사용하면 에러메세지를 throw, keep_window_open() 을 써주면 입력이 없어도 화면 창이 종료되지 않는 등의 여러 유틸리적 기능까지도 제공을 합니다.
결론적으론 그냥 코딩을 간편하게 할 수 있도록 여러 함수와, 기초 선언을 몰라도 사용할 수 있도록 여러 #include문 등이 추가된 헤더파일이라고 보시면 되겠습니다.
번외
// disgusting macro hack to get a range checked vector:
#define vector Vector
그리고 한가지 중요한 점은 이 헤더파일을 include 해서 사용하는 vector class의 경우 우리가 흔히 사용하는 STL 벡터랑은 조금 다른 녀석인데
보시면 매크로로 vector 을 Vector로 정의하고 있습니다. 그니깐 결국 std_lib_facilities.h 를 #include 해서 사용하는 <vector> 의 경우 저자가 따로 Vector 이라고 만들어 논 것을 사용하는 것에 불과하다는 것이죠.
실제로 책 후반부에서 C++에 기본 제공하는 STL의 vector 을 지금까지 배워본 class 개념으로 만드는 내용이 존재하는데 여기서 vector을 따라서 똑같이 만들어 보는게 바로 "Vector" 라는 이름 입니다. (사실 책에선 그냥 vector 라고 쓰긴 하는데 구분을 위해 Vector라고 하겠습니다.)
이 Vector class는 스탠다드한 vector과 거의 차이가 없습니다. 애초에 Vector을 만든 목적도 기존에 C++에 이미 구현되어있는 vector을 따라하는 것이라서요.
마지막에 이런식으로 사실 vector는 Vector 였다.. 라고 고백하는 부분이 있습니다. 입 닫고 있어도 될 걸 고백까지 해주니 참 부끄럽네요 ㅎㅎ
이제 궁금하신 건 Vector랑 vector이 무슨 차이가 있는지 알고 싶으실 겁니다.
차이점은 인덱스 접근을 할때 Vector는 인덱스 범위를 넘어섰는지 항상 검사하고 예외를 throw 하는데
C++에 기본 구현되어 있는 STL vector의 경우엔 인덱스 접근을 할때 인덱스 범위를 넘어섰는지 예외처리가 되어있지 않습니다.
Vector의 경우 원소를 10개 할당된 것에 접근하는 경우 v[10] 으로 접근하면 예외가 throw 되지만 STL vector은 그렇지 않다는 것이죠.
실제로 STL vector의 경우엔 내부적으로 인덱스 접근 [] 의 경우엔 예외처리가 없이 그냥 n번째의 원소를 읽어서 return 하는게 끝이고, 예외 처리가 필요할 경우엔 vector.at이라는 멤버 함수를 이용하면 인덱스 범위 검사를 할 수 있습니다.
이렇게 at에는 검사를 해주고 [] 에서는 검사를 안해주는 이유는 성능때문입니다. 범위를 검사하면서 일일히 예외처리를 하는 비용이 만만치 않거든요. 성능은 떨어지는 대신 높은 안정성이냐 (at), 성능은 높은 대신 낮은 안전성이냐 ([] 연산자) 를 택할 수 있게 모든 경우의 수를 기본적으로 제공하는 것입니다.
그래서 예외처리가 가능한 버전인 at, 아닌 버전인 일반적인 인덱스 접근 [] 연산자가 있는겁니다.
그러나 본 저자는 C++ 을 입문시키는 책이므로 무조건적으로 [] 접근을 할때 예외처리가 되어있는 Vector을 강제했고, 또 예외처리를 강제해서 try - catch 에 대한 학습을 유도시킨 것이라고 보시면 되겠습니다.
말이 너무 어려웠는데 사실 std_lib_facilities.h 를 이용해 사용한 vector은 사실 Vector 였고 이건 일반 C++ STL의 벡터와 다르게 [] 에 인덱스 범위 검사가 들어가 있다.. 정도만 이해하시면 됩니다.
그리고 이 책을 이용해서 공부를 하지 않거나 std_lib_facilities.h 자체를 사용하지 않는 경우라면 알 필요가 없습니다.
그래도 C++ STL 벡터에서 [] 로 접근하는 것과 .at 멤버 함수의 차이점을 아는건 상당히 유용한 정보라고 해서 특별히 기록했습니다.
좋은 참고가 되셨길 바랍니다!
'프로그래밍 > C++' 카테고리의 다른 글
[C++] Natural Sort 사용하기 (0) | 2022.06.14 |
---|---|
[C++] Visual Studio <std::filesystem> 사용하기 (2) | 2022.06.11 |
[C++] while(cin) 으로 자료형이 올바른 값을 계속 입력 받기 (0) | 2022.04.01 |
[C++] new와 delete (0) | 2022.03.12 |
[C++] Lvalue와 Rvalue란? (0) | 2022.03.09 |