[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가 필요합니다.

[C++] 복사생성자, 소멸자, 대입연산자

C++을 학습하던 때에 제법 깊이 있게 생각하며 봤던 C++ 내용 중 복사생성자와 소멸자 그리고 대입연산자가 있었습니다. 마치 스타크래프트에서 등장하는 유닛들의 이름을 한글화 해 놓은 것은 이 3가지에 대해서 정리해 봅니다.

기본적으로 C++은 복사생성자, 소멸자, 대입연산자를 프로그래머가 정의해 놓지 않을 경우 정해진 기본 기능을 수행하는 복사생성자와 소멸자, 대입연산자를 만들어 놓는다고 가정할 수 있습니다.

기본 기능에 대한 복사생성자는 해당 클래스의 맴버 변수의 값을 그대로 복사합니다. 또한 기본 기능에 대한 소멸자는 해당 클래스의 맴버 변수의 소멸자를 호출해 줍니다. 그리고 기본기능에 대한 대입연산자는 해당 클래스의 맴버 변수의 값을 그대로 복사합니다.

여기서 언급된 맴버 변수의 값에 대한 복사라함은 단순한 값의 복사로써 만약 맴버 변수가 포인터일 경우 그 변수가 가르키고 있는 값까지 복사하는것이 아닌 주소값만을 복사한다는 의미입니다. 바로 이 포인트에 대한 부분이 C++에서 복사생성자와 소멸자, 대입연산자의 중요함이 강조되는 부분입니다.

아래의 코드는 이들 세가지(복사생성자, 소멸자, 대입연산자)에 대한 작성방법 및 이들을 제공하지 않았을 경우 어떻게 이들 세가지가 호출되는지를 보여주는 예입니다. 작성방법은 클래스 M에서 파악할 수 있고 호출 순서와 여부는 클래스 X에서 파악할 수 있습니다. 클래스 X는 이들 셋을 정의하고 있지 않으므로 C++에서 기본적으로 제공하는 기능에 대해 수행된다고 확신할 수 있습니다.

먼저 클래스 M에 대한 코드입니다.

class M {
public:
    M() {
        cout << "M::default-ctr is called" << endl;
    }

    M(const M&) {
        cout << "M::copy-ctr is called" << endl;
    }

    ~M() {
        cout << "M::dtr is called" << endl;
    }

    M& operator=(const M&) {
        cout << "M::oper= is called" << endl;
        return *this;
    }
};

여기서 기본생성자(M::default-ctr is called를 출력하는 함수)가 추가로 제공되고 있습니다. C++은 프로그래머가 생성자를 추가하지 않으면 기본생성자를 자동으로 추가해 주지만, 여기서는 복사생성자를 추가하고 있음으로 프로그래머가 직접 기본 생성자를 추가해 주어야 합니다.

다음은 클래스 X이며 이 클래스는 복사생성자와 소멸자, 대입연산자를 프로그래머가 제공하지 않으며 단순히 맴버 변수로써 클래스 M만을 가지고 있습니다.

class X {
    M m;
};

이제 클래스 X를 생성하고 대입하는 코드를 통해 복사생성자와 소멸자, 대입연산자의 호출을 확인해 보도록 하겠습니다.

X x;
X y = x;
x = y;

실행 결과는 다음과 같습니다.

사용자 삽입 이미지
1번 코드에 의해서 M::default-ctr is called가 표시되며 2번 코드에 의해서 M::copy-ctr is called가 호출됩니다. 그리고 3번 코드에 의해서 M::oper= is called가 호출됩니다. 그리고 이 프로그램의 유효범위가 종료됨으로써 직역변수로 선언된 x와 y에 대한 소멸자가 각각 1번씩 호출됩니다.