[C++11] std::array

C++11에서는 STL에 array라는 새로운 컨테이너를 추가 하였습니다. vector 컨테이너와 유사하지만 다음과 같은 차이가 있습니다.

  • vector는 heap을 사용하지만 array는 stack을 사용함
  • vector는 크기가 유동적이지만 array는 크기가 고정임
  • vector보다 array가 item 접근 속도가 빠름

이 array 컨테이너 클래스에 대한 가장 기본적은 사용은 다음과 같습니다.

#include "stdafx.h"
#include 
#include 

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    array arr = {1, 2, 3};

    for(auto i = arr.begin(); i != arr.end(); ++i) {
        cout << *i << endl;
    }
	
    return 0;
}

9번 코드에서 보듯이 array는 타입과 배열의 크기를 갖습니다. 예제에서는 크기를 10으로 지정했는데 실제값은 3개만을 지정하고 있습니다. 이렇게 될 경우 나머지 7개에 대해서는 0의 값으로 채워집니다. 11번 코드는 일반적인 STL의 컨테이너 요소를 순회하며 접근하는 코드를 볼 수 있습니다. array는 vector와 마찬가지로 []를 통해 요소에 접근할 수 있으며 다음 예과 같습니다.

#include "stdafx.h"
#include 
#include 

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    array arr = {1, 2, 3};
	
    cout << "size: " << arr.size() << endl;
    cout << arr[0] << " " << arr[1] << " " << arr[2] << " ..." << endl;

    return 0;
}

C++11에서의 array는 원래의 배열인 C-Array 타입과 전혀 호환되지 않습니다. C++11의 array를 C-Array로의 타입 변환을 위해서는 다음과 같은 예를 통해 가능합니다.

#include "stdafx.h"
#include 
#include 

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    array arr = {1, 2, 3};

    int *pArr = arr.data();
    pArr[1] = 100;
    cout << arr[0] << " " << arr[1] << " " << arr[2] << " ... " << endl;

    return 0;
}

C-Array 타입으로 변환되었을지라도 12번 코드처럼 값을 변경하면 C++11에서의 array 객체에도 적용되는 것을 알 수 있습니다. 이것으로 볼때 C++11의 array는 내부적으로 C-Array를 그대로 사용하는 것을 알 수 있습니다.

[C++11] 유니폼 초기화(Uniform Initializer)

유니폼 초기화는 객체의 초기화를 보다 간단한 코드만으로 구현할 수 있는 기능입니다. 예를 들어 다음과 같은 Product라는 클래스가 있다고 할때 이 Product 클래스의 객체를 초기화하여 생성하는 경우를 예를 들어 보겠습니다.

#include "stdafx.h"
#include 
#include 

using namespace std;

class Product
{
private:
    string name;
    int cost;
public:
    Product(const string& name, int cost) : name(name), cost(cost) {}
};

int _tmain(int argc, _TCHAR* argv[])
{
    Product r("Apple", 100);
	
    return 0;
}

이미 흔히 알고 있는 방식으로 18번 코드와 같이 객체를 초기화하면서 생성하고 있습니다. 이런 초기화 방식에 대해 C++11은 다음과 같은 방식도 지원합니다.

#include "stdafx.h"
#include 
#include 

using namespace std;

class Product
{
private:
    string name;
    int cost;
public:
    Product(const string& name, int cost) : name(name), cost(cost) {}
};

int _tmain(int argc, _TCHAR* argv[])
{
    Product r {"Apple", 100};
	
    return 0;
}

변경된 부분은 15번 코드입니다. 바로 () 대신에 {}를 사용해 초기화를 하고 있습니다. 여기까지는 별 것 없는 것 같습니다만 이러한 초기화 방식이 유니폼 초기화(Uniform Initializer)입니다. 이 별것 없을 것 같은 초기화 방식이 배열이나 콜렉션(Collection, Container)에서는 다음처럼 응용되어 사용할 수 있습니다.

#include "stdafx.h"
#include 
#include 
#include 

using namespace std;

class Product
{
private:
    string name;
    int cost;
public:
    Product(const string& name, int cost) : name(name), cost(cost) {}
};

int _tmain(int argc, _TCHAR* argv[])
{
    vector v {
        {"Apple", 100},
        {"Orange", 200},
        {"Grape", 300}
    };

    return 0;
}

유니폼 초기화를 이용하여 vector 컨테이너에 3개의 객체를 생성하고 있습니다. 이러한 방식을 이용하지 않고 3개의 객체를 초기화하여 컨테이너에 추가하는 기존의 코드를 생각해 본다면 이러한 유니폼 초기화 방식이 얼마나 직관적인지 쉽게 짐작해 볼 수 있습니다.

[C++11] 범위 기반 for 문

for 문은 초기값과 반복조건 그리고 증감(감소)문으로 구성되어 있습니다. C++11에서는 다소 복잡한 for 문 대신 보다 간결한 범위 기반의 for 문을 지원합니다. 아래의 예를 살펴보도록 하겠습니다.

#include "stdafx.h"
#include 
#include 

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    vector v;
    for(int i=0; i<10; ++i)
    {
        v.push_back(i);
    }

    for(vector::const_iterator it = v.cbegin(); it != v.cend(); ++it) {
        cout << *it << endl;
    }

    return 0;
}

15번 코드를 보면 for문에 대한 초기값, 조건값, 증감문으로 구성되어 있는 것을 볼 수 있습니다. 이를 범위 기반의 for 문을 사용하여 다음과 같이 단순하게 코드를 구성할 수 있습니다.

#include "stdafx.h"
#include 
#include 

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    vector v;
    for(int i=0; i<10; ++i)
    {
        v.push_back(i);
    }

    for(int i: v) {
        cout << i << endl;
    }

    return 0;
}

변경된 15번째 줄 코드를 보면 오직 반복하여 얻는 값에 대한 정의만 있습니다. 매우 직관적이라는 것과 반복에 대한 범위를 고민할 필요가 없다는 것을 알 수 있습니다. 이 범위 기반의 for문에 대해 한가지 더 살펴본다면 바로 반복에 대한 참조값의 사용입니다. 위의 코드는 참조값이 아니므로 반복문 안에서 값에 대해 변경할 경우 원본은 변경되지 않는다는 것입니다. 원본의 변경이 필요할 경우 다음과 같이 참조값을 이용하여 for 문을 구성할 수 있습니다.

#include "stdafx.h"
#include 
#include 

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    vector v;
    for(int i=0; i<10; ++i)
    {
        v.push_back(i);
    }

    for(int &i: v) {
         i*= 2;
    }

    for(int i: v) {
        cout << i << endl;
    }

    return 0;
}

15번 코드에서 int &i와 같은 참조 타입을 사용하였으므로 원본값을 변경할 수 있습니다. 위의 코드는 C++11에서 새롭게 정의되어 지원되는 auto 기능을 이용하여 다음처럼 동일한 기능으로 변경할 수 있습니다.

#include "stdafx.h"
#include 
#include 

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    vector v;
    for(auto i=0; i<10; ++i)
    {
        v.push_back(i);
    }

    for(auto &i: v) {
        i *= 2;
    }

    for(auto i: v) {
        cout << i << endl;
    }
	
    return 0;
}

한번 정의된 데이터에 대해서 이후에 굳이 그 타입을 찾아 입력할 필요없이 auto 키워드를 통해 매우 직관적이고 꼭 필요한 사고(Thinking)만을 할 수 있도록 지원하고 있습니다.

[C++11] auto 키워드

원래 auto 키워드는 저장 클래스 지정자(storage class specifier) 중으로 하나로 기본 지정자입니다. 기본 지정라는 의미는 굳이 지정하지 않아도 자동으로 auto로 지정된다는 의미입니다. 그러다보니 거의 사용되지 않는 키워드였는데 C++11에서 이 키워드에 매우 강력한 기능을 부여하게 되었습니다.

C++11에서 auto에 대해 새롭게 부여한 기능으로써 의미는 컴파일 타임에서 자동으로 type을 지정할 수 있도록 하는 것입니다. 아래의 코드를 예로 살펴보면 직관적으로 auto의 의미를 파악할 수 있을 것입니다.

#include "stdafx.h"
#include ;
#include 

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    vector v;
    for(int i=0; i<10; ++i)
    {
        v.push_back(i);
    }

    vector::iterator it = v.begin();
    while(it != v.end()) {
        cout << *it << endl;
        it++;
    }

    return 0;
}

위의 코드는 우리가 흔히 C++11 이전에 써오던 코드입니다. 눈여겨 봐야할 부분은 바로 15번 코드입니다. 반복자(iterator)의 타입을 선언하기 위해 vector::iterator과 같이 제법 긴 타입명을 사용하고 있습니다. 예제이니 그렇지 실제 코드에서는 더욱 더 긴 타입명이 빈번하게 나옵니다. 이를 auto라는 키워드를 통해 다음처럼 단순하게 만들 수 있습니다.

#include "stdafx.h"
#include ;
#include 

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    vector v;
    for(int i=0; i<10; ++i)
    {
        v.push_back(i);
    }

    auto it = vInt.begin();
    while(it != v.end()) {
        cout << *it << endl;
        it++;
    }

    return 0;
}

변경된 부분은 오직 앞서 언급한 15번 코드입니다. 바로 vector::iterator 대신 auto를 사용했습니다. auto로 선언된 데이터 타입은 컴파일 시점에서 컴파일러에 의해 상당히 정확하게 그 타입을 유추해 결정해 줍니다.

PostgreSQL의 확장 기능 HStore

GIS 분야의 개발자로써 PostgreSQL은 정말 좋은 DBMS입니다. 공간DB를 위한 PostGIS의 확장 기능이 단연 으뜸이구요. 평소 PostgreSQL에 대해 많은 관심을 갖던 중 확장 기능으로써 PostGIS 이외에 또 다른 확장 기능이 무엇인지를 살펴보다가 HStore라는 기능을 알게 되었는데요. 이 기능은 Key / Value라는 단순한 구조를 갖는 테이블을 정의할 수 있는 확장입니다.

해서.. 간단히 HStore라는 기능에 대한 사용 예를 정리해 봅니다. HStore는 PostgresSQL에서 기본적으로 제공되므로 PostGIS처럼 별도의 설치는 필요치 않습니다. 저 같은 경우 PostgreSQL 9.3을 사용하고 있습니다. 혹, HStore가 지원되지 않는다고 판단될 경우 최신버전을 사용해 보시기 바랍니다.

설치된 PostgreSQL에서 HStore를 한번도 사용해 보지 않았다면 다음과 같은 명령을 통해 HStore를 활성화 시켜야 합니다. 한번 활성화되면 매번 다시 활성화할 필요가 없습니다.

CREATE EXTENSION hstore;

이제 HStore를 이용해서 간단한 주소록 테이블을 만들어 보겠습니다.

CREATE TABLE AddressBook (
    id serial PRIMARY KEY,    
    name varchar,
    attributes hstore
);

hstore 타입의 attributes 필드가 핵심입니다. 이제 이 테이블에 3개의 레코드를 입력하겠습니다.

INSERT INTO AddressBook (name, attributes) VALUES (
    '김형준',
    'age => 38,
     telephone => "010-9438-3224",
     email  => "hjkim@geoservice.co.kr"'
);

INSERT INTO AddressBook (name, attributes) VALUES (
    '일지매',
    'age => 27,
     telephone => "N/A",
     email  => "jime@korea.kr"'
);

INSERT INTO AddressBook (name, attributes) VALUES (
    '홍길동',
    'age => 18,
     telephone => "N/A",
     email  => "gildong@josun.kr"'
 );

hstore 타입의 attributes 필드의 값을 입력하는 방식이 다수의 Key, Value에 대한 문자열임을 알 수 있습니다. Key와 Value의 구분은 => 를 사용하고 있습니다 !

이제 이렇게 입력한 데이터셋으로부터 데이터 질의(Query)를 해보겠습니다.

SELECT name, attributes FROM AddressBook;

결과는 아래와 같습니다.

다음은 Key에 대해 조건을 걸어 검색해 보겠습니다. Key 중 전화(telephone)가 ‘N/A’ 값으로 입력 된 레코드를 조회하는 것입니다. 아래와 같습니다.

SELECT name, attributes FROM AddressBook WHERE attributes->'telephone' = 'N/A';

결과는 예상했던 것처럼 아래와 같습니다.

이 HStore라는 녀석을 어떻게, 어디에 활용할 수 있을까 생각해 봐야 겠습니다.