엄청 큰 정수 연산을 위한 라이브러리

C++ Bigint class

라는 라이브러리가 있어 소개합니다.

C++ 개발 좀 했다면 쉽게 만들 수 있을지도 모르겠으나 라이브러리 있으면 편하자나요?

unsigned int를 동적 배열로 해서 정수를 적절히 나누어 담은 뒤에 사칙연산을 오버라이딩해서 각 배열 요소에 적용하면서 자리 올림을 처리하면 되지 않을까 싶은데…

생각만해도 귀찮네요. 그냥 누군가 만들어 둔 라이브러리가 있고 잘 동작한다면 그걸 사용하는 게 낫지 않을까요?

virtual 상속

다중 상속으로 인해 발생하는 문제를 보여주는 예제 입니다.

#include <iostream>
using namespace std;
class A
{
public:
 int n;
 A(int a)
 {
 cout << "A::A(): " << a << endl;
 n = a;
 }
};
class B1 : public A
{
public:
 B1() : A(11)
 {
 cout << "B1::B1(): " << A::n << endl;
 }
};
class C1 : public A
{
public:
 C1() : A(12)
 {
 cout << "C1::C1(): " << A::n << endl;
 }
};
class D1 : public B1, public C1
{
public:
 D1()
 {
 cout << "D1::D1()" << endl;
 cout << "B1::n: " << B1::n << endl;
 cout << "C1::n: " << C1::n << endl;
 }
};
class B2 : virtual public A
{
public:
 B2() : A(11)
 {
 cout << "B2::B2(): " << A::n << endl;
 }
};
class C2 : virtual public A
{
public:
 C2() : A(12)
 {
 cout << "C2::C2(): " << A::n << endl;
 }
};
class D2 : public B2, public C2
{
public:
 /*
 아래는 컴파일 에러 발생
 이유는 D2를 생성할 때에 A의 기본 생성자가 없으므로 컴파일러가 오류를 발생시킨다
 D2()
 {
 cout << "D2::D2()" << endl;
 cout << "B2::n: " << B2::n << endl;
 cout << "C2::n: " << C2::n << endl;
 }
 */
 D2() : A(21)
 {
 cout << "D2::D2()" << endl;
 cout << "B2::n: " << B2::n << endl;
 cout << "C2::n: " << C2::n << endl; } }; int main(void) { D1 d1; D2 d2; char ch; cin >> ch;
 return 0;
 /*
 output:
 A::A(): 11
 B1::B1(): 11
 A::A(): 12
 C1::C1(): 12
 D1::D1()
 B1::n: 11
 C1::n: 12
 A::A(): 21
 B2::B2(): 21
 C2::C2(): 21
 D2::D2()
 B2::n: 21
 C2::n: 21
 */
}

위 예제의 출력에서 가장 중요한 것은 각 생성자에서 출력하는 내용입니다.

D1은 11과 12를 출력하고 D2는 21을 출력합니다.
D1은 B1과 C1을 통해서 A 클래스를 위한 메모리 공간을 각각 따로 만들었기 때문에 A::n의 값이 두 종류 11과 12가 되었습니다.

D2는 B2와 C2가 virtual 로 A를 상속했기 때문에 D2 생성자에서 명시적으로 A의 생성자를 호출해야 하며,
A 클래스가 메모리에 1개만 올라가 B2::n, C2::n으로 A::n을 참조할 때에 같은 값인 21이 출력됩니다.

여기서… 정말 더 중요한 것!!!

만약 코딩을 하는데 virtual 상속이 필요한 경우가 있다면… 구조를 다시 한번 되돌아보고 잘못된 것이 없는지 확실하게 확인을 해야 할 것입니다.
Java에서 다중 상속을 버린 것이 그냥 심심해서가 아니기 때문이죠.

다중 상속이 필요하다면 Java에서 인터페이스를 선언하고 구현하는 방법을 추천합니다.
이와 유사한 방법이 COM 인터페이스 정도 될까요?

상속하지 못하는 클래스 만들기

작업을 하다 보면 상속을 절대 못하도록 막고 싶을 때가 있습니다.

ISO C++ 11 표준에 final 키워드가 추가되어 이것을 해결해 주긴 합니다.

// 가상 함수를 오버라이딩 못하게 하기
class A
{
	virtual void func() final;
};

class A2 : public A
{
	virtual void func(); // 컴파일 에러
};

// 클래스 자체를 상속 못하게 하기
class B final
{
};

class B2 : public B // 컴파일 에러
{
};

하지만 C++ 11 을 제대로 지원하지 못하는 환경에서는 어떻게 할까요?
Visual Studio를 사용한다면 Windows Runtime(/ZW)이나 CLR(/clr) 옵션을 켜도 sealed 키워드를 사용할 수 있습니다.

// 가상 함수를 오버라이딩 못하게 하기
ref class A
{
	virtual void func() sealed;
};

ref class A2 : public A
{
	virtual void func() sealed; // 컴파일 에러
};

// 클래스 자체를 상속 못하게 하기
ref class B sealed
{
};

ref class B2 : public B // 컴파일 에러
{
};

하지만 이 환경도 안되는 경우 어떻게 할까요?

아래와 같은 내용으로 헤더 파일을 만든 뒤에

class __NonDerivableClass
{
protected:
	__NonDerivableClass() {}
	__NonDerivableClass(const __NonDerivableClass&) {}
};
#define nonderivable private virtual __NonDerivableClass

이런 식으로 사용하시면 됩니다.

class A : nonderivable
{
};

하지만 가상 함수 오버라이딩은 막을 수가 없습니다. ㅠㅠ

음… 그냥… 최신 컴파일러를 사용하시는 게 더 좋아보이네요.

boost를 사용해서 UTF-8로 텍스트 파일을 다루기

이런 저런 이유로 대부분 UTF-8 텍스트 파일을 많이 사용하게 됩니다.


그런데 C++, 특히 Windows 환경에서는 참으로 불편하기 짝이 없습니다.


그래서 아래와 같은 방법을 사용하게 되었습니다.


// 테스트 파일명은 test.txt
std::wstring filename(L"test.txt");

// 파일을 열고
// (BS만의 다른 이유로 생성자를 통해 열지 않고 open() 메서드를 사용했습니다.)
boost::filesystem::wofstream ofs;
ofs.open(boost::filesystem::wpath(filename));

// UTF-8 BOM을 남기고
ofs << (wchar_t)0xef;
ofs << (wchar_t)0xbb;
ofs << (wchar_t)0xbf;

// 로케일 설정
std::locale loc = boost::locale::generator()("ko_KR.UTF-8");
ofs.imbue(loc);

// 쓰기
ofs << L"하하하" << std::endl;

// 닫기
ofs.close();

// 파일을 열고
boost::filesystem::wifstream ifs;
ifs.open(boost::filesystem::wpath(filename));

// 로케일 설정하고
ifs.imbue(loc);

// 읽어 봅시다
std::wstring result;
ifs >> result;


잘 되네요… 하하.. boost 최고!


그런데 수동으로 넣어줘야 했던 내 BOM은 읽을 때에 어디갔니?

boost property_tree로 UTF-8로 인코딩된 xml 읽고 쓰기

업데이트 2013-10-22) 좀 더 보기 좋은 방법을 추가했습니다.


BS가 간단한 유틸을 만들면서 설정파일을 매번 INI 형식을 사용하다가 이번에는 xml을 사용하였습니다.
사용한 라이브러리는 boost의 property tree
그런데 이렇게 무심코 사용했더니…


xml 헤더에는 인코딩이 utf-8이라고 나오지만 실제로는 Codepage 949의 ANSI 파일로 저장되네요.


그래서 UTF-8로 저장하기 위해서 아래와 같은 방법을 사용했습니다.

// 읽기
std::wstringstream ws;
ws.imbue(std::locale(std::locale::classic(), ".OCP", std::locale::ctype | std::locale::collate));
FILE* fp = nullptr;
if (_wfopen_s(&fp, _filepath.c_str(), L"rt, ccs=UTF-8") != 0)
	return false;
wchar_t buf[1024];
while (fgetws(buf, 1024, fp) != nullptr)
{
	ws << buf;
}
fclose(fp);
ws.seekg(0);
boost::property_tree::xml_parser::read_xml(ws, _settings, boost::property_tree::xml_parser::trim_whitespace);

// 쓰기
std::wostringstream wos;
wos.imbue(std::locale(std::locale::classic(), ".OCP", std::locale::ctype | std::locale::collate));
boost::property_tree::xml_parser::write_xml(wos, _settings, boost::property_tree::xml_writer_make_settings(L'\t', 1));
FILE* fp = nullptr;
if (_wfopen_s(&fp, _filepath.c_str(), L"wt, ccs=UTF-8") != 0)
	return;
fputws(wos.str().c_str(), fp);
fclose(fp);

메모리 상에서는 UCS16-LE로 하고 파일 입출력은 UTF-8로 하였습니다.
Windows에서 setlocale()이 UTF-8을 지원하면 저렇게 안해도 될텐데 말이죠…


좀 보기 안 좋습니다.


그래서 아래처럼 해봤습니다.


잘 되네요. (읽기는… 쓰기는 테스트 안했습니다. ㅎㅎㅎㅎㅎ 그냥 output stream에 L”가나다라” 했더니 BOM없는 UTF-8 문서로 저장은 잘 되네요.)

// 읽기
std::wifstream fin(_filepath.c_str());
fin.imbue(std::locale(fin.getloc(), new std::codecvt_utf8<wchar_t>));
boost::property_tree::xml_parser::read_xml(fin, _settings, boost::property_tree::xml_parser::trim_whitespace);

// 쓰기
std::wofstream fout(_filepath.c_str(), new std::codecvt_utf8<wchar_t>));
boost::property_tree::xml_parser::write_xml(fout, _settings, boost::property_tree::xml_writer_make_settings(L'\t', 1));

C++ Debugger Visualizers – boost, TinyXML, wxWidgets를 쉽게 디버깅하기

[Microsoft Visual Studio Gallery의 프로그램 소개 페이지]
[CodePlex의 개발 페이지]

Visual Studio 2012를 사용하신다면 필수 확장 프로그램입니다.
boost나 TinyXML등을 사용하는 프로그램을 디버깅할 때에
설치만으로도 편리하게 조사식에서 데이터를 확인할 수 있도록 해줍니다.

최근 C++ 프로그래머들에게 인기를 끌고 있는 boost가 디버깅시에 컨테이너 내부의 데이터 보기가 불편해서
디버깅 버전은 MS의 STL이나 직접 만든 간단한 컨테이너로 하고
릴리즈 버전은 boost를 쓴다거나 했었는데…

이거 한방이면 해결됩니다. ㅋㅋㅋ

복사 생성 방지용 클래스

C++ 클래스를 만들다보면 복사 생성자나 할당 연산자로 복제하지 않기를 희망하는 경우가 있습니다.
매번 코딩하려면 너무 귀찮네요.

아래 클래스를 상속 받으면 복사 방지 됩니다.

class NonCopyable
{
protected:
	NonCopyable() {}
	~NonCopyable() {}
private:
	NonCopyable(NonCopyable const &);
	NonCopyable const & operator=(NonCopyable const &);
};

초간단 Boost 빌드 방법 – 1.53.0 + Visual Studio 2012 + Windows 8

BS가 Windows 8에 VS2012를 설치했습니다.
그래서 boost를 다시 빌드하려니 이전 방법 그대로는 안되더라구요.
다시 정리했습니다.

일단 이전 포스트에서 소개했던 [boostpro]!!!

Thank You
After 12 years in business, the principals of BoostPro have decided it’s time for new challenges. …

가서 보시면 알겠지만 구성원들이 FPComplete, Apple 등으로 이직했다는군요.
이제 빌드를 하지 않으면 구할 수 없게 되었습니다!!!???
서론을 마치고 직접 빌드를 해보겠습니다.

  1. 소스받기
    [Boost Libraries]에 가서 최신 버전을 받습니다.
    되도록이면 7z 포맷을 받으세요. 용량이 좀더 작습니다.
  2. 압축 풀기
    원하는 곳에 풀어줍니다.
    BS는 일단 C:\Works\Libraries\boost 라고 했습니다. 이 경로를 기준으로 설명합니다.
  3. b2.exe 만들기

    VS2012 x86 Native Tools Command Prompt를 실행합니다.

    Windows 8을 사용하시는 분은 WinKey + Q를 눌려 VS2012를 검색하시면 됩니다.
    이전 버전이라면 그냥 WinKey 누르면 나오는 검색에서 찾으시면 됩니다.
    아래 명령을 실행합니다.

    CD /D C:\Works\Libraries\boost
    bootstrap.bat

  4. 소스파일 수정
  5. 아직도! 그대로인 내용

    boost_1_53_0/boost/format/alt_sstream_impl.hpp의 176라인의 주석문에 CP949에서 불가능한 문자 포함


    boost_1_53_0/boost/tuple/tuple_io.hpp의 138라인의 const char c가 const CharType c가 되어야 하는 것 아닌지

    그 외의 부분은 BS가 사용하면서 찾지 못했습니다.
    BS가 모든 라이브러리를 사용하는 것이 아니라서요.

  6. 빌드
    BS는 b2.exe로 install 하지 않습니다.
    위에서 압축을 해제한 그 폴더 그대로 사용합니다.
    문서도 보기 편하고 해서요.
    물론 일부는 삭제합니다. 빌드 임시 파일 같은 것들… (bin.v2 디렉토리)
    아래는 boost라이브러리는 static library로 하고, C Runtime Library는 Multi Thread DLL(MTD)을 사용하며
    debug, release 버전을 생성하도록 합니다.

    x86 빌드

    b2 -a –stagedir=x86 variant=debug,release link=static threading=multi stage

    x64 빌드

    b2 -a –stagedir=x64 variant=debug,release link=static threading=multi address-model=64 stage

    당연한 이야기이지만 64비트 빌드를 위해서는 VS2012 x64 Native Tools Command Prompt를 실행해서 빌드해야 합니다.

  7. 기타
  8. Visual Studio 가 여러 버전 설치되어 있다면
    project-config.jam 파일에서

    using msvc ; 을 using msvc : version ; 으로 변경하시면 됩니다.

    예) VS2008
    using msvc : 9.0 ;

참고사항) 위 방법으로는 추가 라이브러리가 필요한 일부 기능이 제한됩니다.
참고사항) 자세한 빌드 방법이 궁금하시다면 우선 b2.exe –help 부터
참고사항) [Visual C++ 관련 문서]

Visual C++ 2010의 새기능 – 람다 표현식(Lambda Expression)

Visual Studio 2010이 발표된지 꽤 오랜 시간이 지났는데 이제야 이런 포스팅을 올립니다.

Visual C++ 2010은 새로운 C++ 표준안이 나오기 전 거의 확정된 일부 기능을 포함시켰는데요.
그중 하나가 람다 표현식(Lambda Expression) 입니다.
그럼 그 람다 표현식에 대해 알아 보겠습니다.

1. 익명 함수
람다 표현식은 익명 함수 오브젝트를 만드는 문법으로,
익명 함수라는 것은 함수의 본문은 있으나 이름이 없는 것을 말합니다.

2. 함수 오브젝트(Function Object 또는 Functor)
C에서의 함수는 어떤 명령이 정의되어 있는 포인터로 해석할 수 있습니다.
이 포인터는 대부분 컴파일과 링크 과정에서 이름과 연결되게 되어 있습니다.
필요한 경우 함수 포인터 변수에 런타임에 지정하여 사용하기도 했습니다.
예를 들면 Windows API에서 사용하는 수많은 콜백 함수들이 있죠.
C++ 에서는 이런 함수의 기능에 좀 더 기능을 추가한 함수 오브젝트를 제공하였습니다.
함수 오브젝트는 단순한 함수에 현재의 상태를 저장할 수 있는 것이 큰 차이점 입니다.

3. 함수, 함수 오브젝트, 람다 표현식
모두 함수라는 공통점을 가지고 있습니다.
함수는 문법 오버헤드가 적은 대신에 상태를 저장할 수 없습니다.
함수 오브젝트는 상태 저장은 가능하지만 클래스 정의에 따른 문법 오버헤드가 필요합니다.
람다 표현식은 함수 오브젝트처럼 상태 저장이 가능하지만 클래스 정의를 위한 문법 오버헤드는 없습니다.
따라서 함수 오브젝트를 사용하는 많은 코드에서 기존 함수 오브젝트보다 람다 표현식이 더 깔끔하게 코드를 작성할 수 있습니다.

4. 문법 소개
람다 표현식은
[<캡처>](<파라미터>) <mutable 정의> <throw 정의> <return 정의> { <함수 본문> }
으로 정의됩니다. 이제 하나씩 알아 보겠습니다.

4.1. 캡처
람다 표현식은 정적 변수에는 언제나 접근이 가능합니다. 이는 일반 함수와 동일하죠.
그외 변수에 접근하기 위해 컴파일러에게 알려주는 표현이 캡처입니다.
캡처에는 복사 캡처와 참조 캡처가 있습니다.
[]: 어떤 변수도 캡처하지 않습니다.
[=]: 람다 외부 변수 모두 복사 캡처, 클래스 멤버는 참조 캡처
[&]: 람도 외부 변수 모두 참조 캡처, 클래스 멤버는 참조 캡처
[this]: 클래스 멤버만 참조 캡처
[var]: 람다 외부의 var 변수를 복사 캡처
[&var]: 람다 외부의 var 변수를 참조 캡처
[=, &var]: 람다 외부의 var 변수는 참조 캡처, 그외 변수는 복사 캡처
[&, var]: 람다 외부의 var 변수는 복사 캡처, 그외 변수는 참조 캡처

4.2 파라미터
파라미터 정의는 일반 함수와 거의 유사합니다. 단 아래와 같은 것은 불가능 합니다.
기본값 정의 불가능: [](int n = 10) {} (X)
가변 길이 파라미터 불가능: [](const char* fmt, …) {} (X)
이름 없는 파라미터 불가능: [](int) {} (X)

4.3. mutable 정의
람다 표현식의 함수 본문 안에서 복사 캡처한 변수의 값은 수정할 수 없습니다.
복사 캡처한 변수는 마치 const 와 같이 인식하는 것이죠.
const 멤버 함수에서 멤버 변수 값을 수정하듯이 mutable 키워드를 명시하면 복사 캡처된 변수의 값을 수정할 수 있습니다.
[=var]() { var += 1; } (X)
[=var]() mutable { var += 1; } (O)

4.4. throw 정의
C/C++의 throw 정의와 똑같습니다.
throw() 하면 함수에서 예외를 throw 하지 않는다가 되는 것이고
throw(…) 하면 뭔가를 throw 한다가 됩니다.
Visual C++에서는 저 두가지만 유효합니다.
throw(Exception) 뭐 이런 것은 throw(…)하고 동일하게 인식합니다.
throw 정의는 컴파일 옵션의 예외 처리 옵션과 연관이 있사오니 관련 문서를 참조하세요.

4.5. return 정의
생략하게 되면 void라고 생각하세요.
물론 생략하면서 특정 값을 반환할 수도 있습니다. 예를 들면
[](int n) { return (n < 10); }
와 같이 함수 본문이 달랑 return 하나로만 되어 있을 경우 컴파일 타임에 해당 반환값을 판단하여 return 타입을 정해 줍니다.
하지만… 코드를 읽는 입장에서 return을 정의하지 않고 사용한다면 헷갈리고 좋지 않을 수 있으니 반환을 사용하는 람다 표현식에서는 꼭 명시하세요.
위 예제에서 return 을 정의한다면
[](int n) -> bool { return (n < 10); }
이 되겠습니다.
-> 반환 타입
이렇게 되는 것인데 반환 타입은 void를 제외한 데이터 타입입니다.

4.6. 함수 본문
일반 함수의 본문 정의와 다를 바가 없습니다.
재귀 호출도 가능합니다.

5. 예제: 람다 표현식과 함수 오브젝트 코드 비교
아래 예제를 통해 람다 표현식과 기존 함수 오브젝트의 코드를 비교해 보세요.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int usingLambda(vector<int>& v)
{
    int ret = 0;
    std::for_each(v.begin(), v.end(), [&ret](int n) {
        if (n < 10)
            ret += n;
    });
    return ret;
}

int usingFunctionObject(vector<int>& v)
{
    struct Functor
    {
        Functor(vector<int>& v, int& ret) : v(v), ret(ret) {}
        void operator()(int n)
        {
            if (n < 10)
                ret += n;
        }
        vector<int>& v;
        int& ret;
    };

    int ret = 0;
    std::for_each(v.begin(), v.end(), Functor(v, ret));
    return ret;
}

int main(void)
{
    vector<int> v;
    for (int i = 1; i <= 20; ++i)
        v.push_back(i);

    cout << usingLambda(v) << endl;
    cout << usingFunctionObject(v) << endl;

    return 0;
}

6. 예제: 람다 표현식 예제

#include <iostream>

using namespace std;

int gVar = 0;

class A
{
public:
    A() : n(10) {}
    void f1()
    {
        // this를 캡처했지만 gVar로 변경됩니다.
        [this](){ ++n; cout << n << endl << gVar++ << endl; }();
    }
    void f2()
    {
        // = 로 복사 캡처 했지만 클래스 멤버 n의 값이 변경됩니다.
        [=]() { ++n; cout << n << endl << gVar++ << endl; }();
    }
    void g()
    {
        cout << n << endl;
        // 아무것도 캡처하지 않았지만 gVar를 참조할 수 있습니다.
        [](){ cout << gVar << endl; }();
        // 단일 반환문으로 된 본문은 반환 타입 정의를 생략할 수 있습니다.,
        bool b = [](int n) { return (n < 10); }(10);
        cout << b << endl;
    }
private:
    int n;
};

int main(void)
{
    A a;
    a.f1();
    a.f2();
    a.g();


    /*
        output:
            11
            0
            12
            1
            12
            2
            0
    */
    return 0;
}

C++11의 새로운 모습들…

Sutter’s Mill Blog에 참 친절하게 요약을 하셨네요.

Visual Studio 2010에서는 일부만 가능하지만요…

앞으로 나올 C++ 컴파일러는 표준을 따를테니 참고하세요.

큰 변화로

auto 키워드
Smart Pointers
nullptr 키워드
for 키워드 (새로운 range for)
비멤버 begin(), end()
람다 표현식
move 연산
Uniform 초기화와 리스트 초기화

등에 대해 소개되어 있습니다.