[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로 선언된 데이터 타입은 컴파일 시점에서 컴파일러에 의해 상당히 정확하게 그 타입을 유추해 결정해 줍니다.

[C++] URL로부터 바이너리 데이터 다운로드

예전에 만들어 놓은 것이 있는데.. 도통 찾을 수가 없어서 다시 만들어 본 함수입니다. URL 경로에 존재하는 데이터를 다운로드하여 로컬 파일로 저장해 주는 함수입니다. 실제 개발에 사용할 요량으로 인자가 제법 복잡합니다.

DWORD Download(HINTERNET hInternet, char *pszURL, 
    char *pszFileName, BYTE *pBuffer) {
    HINTERNET hURL = InternetOpenUrl(hInternet, pszURL, NULL, 0, 0, 0);
    if(hURL == NULL) {
        InternetCloseHandle(hInternet);
        return -2;
    }

    HANDLE hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, 
        NULL, CREATE_ALWAYS,     FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile == INVALID_HANDLE_VALUE) return -3;

    DWORD dwSize = 2048;
    DWORD dwRead, dwWritten, dwTotalSize;

    bool bOK = HttpQueryInfo(hURL, HTTP_QUERY_CONTENT_LENGTH, 
        pBuffer, &dwRead, NULL);
    if(!bOK) return -4;
    dwTotalSize = atoi((const char *)pBuffer);

    do {
        InternetQueryDataAvailable(hURL, &dwSize, 0, 0);
        InternetReadFile(hURL, pBuffer, dwSize, &dwRead);
        WriteFile(hFile, pBuffer, dwRead, &dwWritten, NULL);
    } while(dwRead != 0);

    InternetCloseHandle(hURL);
    CloseHandle(hFile);

    return dwTotalSize;
}

아래는 위의 함수를 직접 사용하는 코드입니다.

HINTERNET hInternet = InternetOpen("MyAGENT", INTERNET_OPEN_TYPE_PRECONFIG, 
    NULL, NULL, 0);
if(hInternet == NULL) return 0;

BYTE *pBuffer = new BYTE[1024*1024];
DWORD dwTotalSize;
 
dwTotalSize = Download(hInternet, "http://www.s.com/a.zip", "c:/a.zip", pBuffer);
printf("TotalSize: %d\n", dwTotalSize);

dwTotalSize = Download(hInternet, "http://www.s.com/b.zip", "c:/b.zip", pBuffer);
printf("TotalSize: %d\n", dwTotalSize);

delete [] pBuffer;
InternetCloseHandle(hInternet);

1번 코드에서처럼 가장먼저 hInternet 객체를 만듭니다. 이 객체를 재활용하여 다수의 URL을 통해 파일을 다운로드할 수 있습니다. 5번 코드는 파일을 다운로드하는데 사용하는 버퍼입니다. Download 함수를 여러번 사용할 것을 대비하여 버퍼를 재활용할 수 있도록 하였습니다. 또한 Downoad 함수의 결과값은 다운로드된 바이너리 데이터의 전체 바이트 수입니다. 음수인 경우 ERROR로 간주할 수 있습니다. 14번과 15번 코드처럼 사용한 리소스는 반환합니다.

추가로 아래의 Download2 함수는 다운로드된 데이터를 파일로 기록하지 않고 메모리 버퍼에 저장하는 함수입니다. 다운로드된 데이터를 파일에 저장하지 않고 바로 메모리 상에서 사용하고자 할때 사용할 수 있습니다.

DWORD Download2(HINTERNET hInternet, char *pszURL, BYTE *pBuffer) {
    HINTERNET hURL = InternetOpenUrl(hInternet, pszURL, NULL, 0, 0, 0);
    if(hURL == NULL) {
        InternetCloseHandle(hInternet);
        return -2;
    }

    DWORD dwSize;
    DWORD dwRead, dwWritten, dwTotalSize;
    DWORD dwCursor = 0;

    bool bOK = HttpQueryInfo(hURL, HTTP_QUERY_CONTENT_LENGTH, pBuffer, 
        &dwRead, NULL);
    if(!bOK) return -4;
    dwTotalSize = atoi((const char *)pBuffer);

    do {
        InternetQueryDataAvailable(hURL, &dwSize, 0, 0);
        InternetReadFile(hURL, (LPVOID)(pBuffer + dwCursor), dwSize, &dwRead);
        dwCursor += dwRead;
    } while(dwRead != 0);

    InternetCloseHandle(hURL);

    return dwTotalSize;
}

끝으로 이 함수 사용을 위해 헤더 파일로 wininet.h와 라이브러리 파일로 wininet.lib가 필요합니다.