반응형


    1. 표준 입력와 출력

     

    모니터와 키보드를 콘솔(console)이라고 하는데, 이것들은 컴퓨터의 기본적인 입출력을 담당하는 표준 입출력(standard input and output) 장치입니다. 모니터로 데이터를 출력하는 방법과 키보드로 데이터를 입력받는 방법에 관하여 살펴봅시다.

     

    C++에서는 파일(장치도 파일로 간주)을 다루기 위해 스트림(stream)이라는 개념을 도입하였습니다. 스트림은 데이터가 이동하는 파이프와 같은 것으로 목적지까지 데이터를 순차적으로 전송하는 역할합니다. 또한 스트림은 데이터를 보내는 속도와 받아서 처리하는 속도의 차를 극복하기 위한 버퍼의 역할도 합니다. 스트림은 데이터의 전송 방향에 따라 출력 스트림과 입력 스트림으로 나누지고, 제작하고 있는 프로그램을 기준으로 밖으로 데이터를 보내면 출력, 외부에서 데이터를 가져오면 입력 스트림입니다. 이러한 스트림을 사용하기 위해서는 스트림을 만들고, << 와 >> 연산자를 사용하여 데이터를 전송하고, 사용 후에는 제거해 줘야 합니다. 표준 입출력을 위한 스트림은 만들고 제거하는 과정없이 항상 사용할 수 있는데, 다음과 같이 준비되어 있습니다.

     

    종류

     스트림

     연산자

    출력

     cout

     <<

    입력

     cin

     >>

     

    스트림을 사용하기 위해서는 먼저 해당 헤더 파일을 소스에 포함시켜야 하고, 이름 공간의 사용을 명시할 필요가 있습니다.

     
    #include <iostream>  // 헤더 파일
    using namespace std; // std 이름 공간 사용
     

    입력 스트림을 사용할 때는 입력된 데이터를 보관할 변수(확보된 메모리 공간)를 미리 준비해야 한다는 것입니다.
    예를 들어 친구의 이름과 나이를 입력 받아, 출력해 봅시다. 

     
    #include <iostream>
     
    using namespace std;
     
    int main()
    {
        char name[9]; // 이름을 입력 받기 위한 변수
        int  age;     // 나이를 입력 받기 위한 변수
     
        cout << "친구 이름 : ";
        cin >> name;
        cout << "친구 나이 : ";
        cin >> age;
     
        cout << "친구의 이름은 " << name << "이고, " 

             << "나이는 " << age << "입니다." << endl;
     
        return 0;
    }

     

    친구의 이름과 나이를 입력받으려면, 먼저 이를 위한 메모리 공간을 확보해야 합니다. 물론 가장 쉬운 방법이 변수를 만드는 것입니다. 먼저 이름을 저장할 변수를 만들어 봅시다. 이름은 문자열이므로 문자 배열이 필요합니다. 한글 4 자를 저장하려면 8 바이트가 필요하고 NULL문자를 위한 공간도 필요하므로 char[9]인 자료형이 필요합니다. 나이는 정수이므로 int 형 변수를 만들면 되겠습니다.

    반응형
    반응형

    /***
    *strcmp.c - routine to compare two strings (for equal, less, or greater)
    *
    *       Copyright (c) Microsoft Corporation. All rights reserved.
    *
    *Purpose:
    *       Compares two string, determining their lexical order.
    *
    *******************************************************************************/

    #include <cruntime.h>
    #include <string.h>

    #ifdef _MSC_VER
    #pragma function(strcmp)
    #endif  /* _MSC_VER */

    /***
    *strcmp - compare two strings, returning less than, equal to, or greater than
    *
    *Purpose:
    *       STRCMP compares two strings and returns an integer
    *       to indicate whether the first is less than the second, the two are
    *       equal, or whether the first is greater than the second.
    *
    *       Comparison is done byte by byte on an UNSIGNED basis, which is to
    *       say that Null (0) is less than any other character (1-255).
    *
    *Entry:
    *       const char * src - string for left-hand side of comparison
    *       const char * dst - string for right-hand side of comparison
    *
    *Exit:
    *       returns -1 if src <  dst
    *       returns  0 if src == dst
    *       returns +1 if src >  dst
    *
    *Exceptions:
    *
    *******************************************************************************/

    int __cdecl strcmp (
            const char * src,
            const char * dst
            )
    {
            int ret = 0 ;

            while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
                    ++src, ++dst;

            if ( ret < 0 )
                    ret = -1 ;
            else if ( ret > 0 )
                    ret = 1 ;

            return( ret );
    }


    반응형
    반응형

    printf를 MSDN에서 찾아보면

     

    If the argument corresponding to a floating-point specifier is infinite, indefinite, or NaN, printf gives the following output.


    위의 내용을 해석하면...

    ( 만약 플로팅지정자로 일치하는 아규먼트가 무한대, 정의되지 않는 숫자(orNaN), 일경우

    printf 는 다음 출력구문을 보여준다  )



    ms 페이지에서 보면 해당 타입의 std::numeric_limits<T>::has_infinity


    의 has_infinity 가 true 일 경우 이러한 무한대 값을 표시해준다라고 나와 있다




    출처 : 지식인



    ValueOutput
    + infinity1.#INFrandom-digits
    – infinity–1.#INFrandom-digits
    Indefinite (same as quiet NaN)digit.#INDrandom-digits
    NAN

    digit.#NANrandom-digits

     

    이렇게 되어 있네요

     

    실수를 프린트에서 찍을때

    +무한은 1.#INF

    -무한은 -1.#INF

    정의되어 있지않은 숫자는  #IND

    NAN(Not a Number)는 #NAN이라고 표시된답니다.

     

    무한(Infinity)은 말 그대로 컴퓨터의 double로 표시할 수 있는 범위를 넘어간 큰수흘 의미 합니다.

    미정의 숫자(Indefinite)는 루트(-1)처럼 정의할 수 없는 숫자를 의미합니다.

    이런 에러가 발생했는 지를 찾기 위해서는 float.h에 있는 _isnan이나 _finite 함수를 이용할 수 있습니다. 다음 예를 참고하십시요

     

    #include < float.h>
    #include < math.h>
    #include < stdio.h>


    void testnumber(double val)
    {
        if(_isnan(val))
        {
            printf("%e은 숫자가 아닙니다.\n",val);
        }else if(!_finite(val))
        {
            printf("%e은 유한 범위의 숫자가 아닙니다.\n",val);
        }
        else
            printf("%e은 정상적인 숫자입니다.\n",val);


    }

    void main()
    {
        double val=1;

        // indefinite   
        testnumber(sqrt(-1));
       

        // infinity
        for(int i=0;i<10;i++)
        {
            val=val*1000000000000000000000000000000000.0;
            testnumber(val);
        }

    }

     

    반응형
    반응형

    대입연산자 오버로딩은 클래스 멤버로만 정의 가능하고 전역으로는 할 수 없다


    정적으로도 만들 수 없다.




    http://winapi.co.kr

    28-3-다.대입 연산자

    대입 연산자는 자신과 같은 타입의 다른 객체를 대입받을 때 사용하는 연산자이다. 객체 자체와 직접적인 연관이 있기 때문에 클래스의 멤버 함수로만 정의할 수 있으며 전역 함수로는 정의할 수 없다. 정적 함수로도 만들 수 없고 반드시 일반 멤버 함수로 만들어야 한다. 다음 예제는 앞장에서 만들었던 Person2예제에 디폴트 생성자를 추가하고 main 함수의 테스트 코드를 약간 수정한 것이다. Person 클래스는 생성자에서 동적으로 버퍼를 할당한다는 점에서 Time이나 Complex 클래스와는 다르며 이 버퍼를 주의깊게 다루어야 할 필요가 있다.

     

      : Person3

    #include <Turboc.h>

     

    class Person

    {

    private:

         char *Name;

         int Age;

     

    public:

         Person() {

              Name=new char[1];

              Name[0]=NULL;

              Age=0;

         }

         Person(const char *aName, int aAge) {

              Name=new char[strlen(aName)+1];

              strcpy(Name,aName);

              Age=aAge;

         }

         Person(const Person &Other) {

              Name=new char[strlen(Other.Name)+1];

              strcpy(Name,Other.Name);

              Age=Other.Age;

         }

         ~Person() {

              delete [] Name;

         }

         void OutPerson() {

              printf("이름 : %s 나이 : %d\n",Name,Age);

         }

    };

     

    void main()

    {

         Person Boy("강감찬",22);

         Person Young("을지문덕",25);

         Young=Boy;

         Young.OutPerson();

    }

     

    Person2 예제에서는 Young 객체를 선언할 때 Person Young=Boy; 형식으로 선언하면서 동시에 초기화를 했었다. 이때는 복사 생성자가 호출되는데 Person2예제에 복사 생성자가 작성되어 있으므로 이 코드는 이상없이 잘 동작한다. 그러나 일단 선언한 후 대입을 받게 되면 문제가 달라진다. 이 예제를 실행해 보면 프로그램이 종료될 때 다운되는 것을 확인할 수 있다.

    선언과 동시에 다른 객체로 초기화하면 이때 복사 생성자가 호출되고 복사 생성자는 새로 생성되는 객체를 위해 별도의 버퍼를 준비하므로 두 객체가 버퍼를 따로 가져 아무런 문제가 없다. 그러나 실행중에 이미 사용중인 객체를 다른 객체로 대입할 때는 초기화 단계가 아니므로 복사 생성자는 호출되지 않는다. 다음 두 경우를 잘 구분하자.

    대입은 ① 이미 생성된 객체에 적용된다. ② 실행중에 언제든지 여러 번 대입될 수 있다는 점에서 초기화와는 다르다. 실행중에 객체끼리 대입 연산을 하면 어떤 일이 벌어지는지 보자.

    깊은 복사를 하는 대입

    대입 연산자를 별도로 정의하지 않을 경우 컴파일러는 디폴트 대입 연산자를 만드는데 이 연산자는 디폴트 복사 생성자와 마찬가지로 단순한 멤버별 대입만 한다. 우변 객체의 모든 멤버 내용을 좌변 객체의 대응되는 멤버로 그대로 대입함으로써 얕은 복사만 하는 셈이다. 결국 Young의 Name 멤버는 Boy의 Name 멤버가 가리키는 버퍼의 주소를 그대로 가지게 될 것이다. 이때의 메모리 상황을 그림으로 그려 보자.

    두 객체 모두 "강감찬"을 가리키고 있으며 main 함수가 종료될 때 각각의 파괴자가 호출되는데 먼저 파괴되는 객체가 Name 버퍼를 정리할 것이고 나중에 파괴되는 객체가 이 버퍼를 이중으로 정리하려고 하므로 무효해진 메모리를 해제하는 오류를 범하는 것이다. 결국 이 문제는 복사 생성자를 정의하지 않았을 때의 문제와 동일하며 생성과 동시에 초기화할 때처럼 대입을 받을 때도 깊은 복사를 하도록 해야 한다.

    뿐만 아니라 생성할 때와는 달리 대입 연산은 실행중에 언제든지 여러 번 일어날 수 있기 때문에 객체가 사용중이던 메모리를 해제하지 않으면 다시는 이 메모리에 접근할 수 없는 문제도 있다. 위 그림에서 Young이 Boy를 대입받은 후 "을지문덕"은 더 이상 읽지도 쓰지도 못하며 해제할 방법조차 없다. 동적으로 할당한 메모리는 포인터가 진입점인데 이 진입점을 잃어버린 것이다. 이런 문제들을 해결하려면 = 연산자를 오버로딩하여 대입할 때도 깊은 복사를 하도록 해야 한다. Person 클래스에 다음 멤버 연산자 함수를 추가해 보자.

     

    class Person

    {

         ....

        Person &operator =(const Person &Other) {

            if (this != &Other) {

               delete [] Name;

               Name=new char[strlen(Other.Name)+1];

               strcpy(Name,Other.Name);

               Age=Other.Age;

            }

            return *this;

        }

    };

     

    복사 생성자의 코드와 유사한 코드가 반복되는데 대입되는 Other의 Name 길이+1만큼 버퍼를 새로 할당한 후 내용을 복사했다. Age는 단순한 정수형 변수이므로 그냥 대입하기만 하면 된다. 복사 생성자와 마찬가지 방법으로 깊은 복사를 하되 대입 동작은 실행중에 여러 번 그것도 임의의 순간에 발생할 수 있기 때문에 좀 더 신경써야 할 것들이 많다.

    우선 Name 멤버를 할당하기 전에 이전에 사용하던 메모리를 먼저 해제해야 한다. 복사 생성의 경우 Name은 새로 만들어지는 중이므로 할당되어 있지 않지만 대입은 사용중인 객체에 대해 일어나는 연산이므로 Name이 이미 할당되어 있을 것이다. 다른 객체를 대입받는다는 것은 이전의 내용을 버린다는 뜻이므로 이미 할당된 메모리를 해제할 필요가 있는데 이 처리를 하지 않으면 대입할 때마다 이전에 사용하던 메모리가 누수될 것이다. 그래서 new 연산자로 Name을 할당하는 코드 앞에 delete [] Name이 필요하다. 이때 Name이 이미 할당되어 있는지는 점검할 필요가 없는데 디폴트 생성자가 1바이트를 할당하고 있으므로 Name은 항상 동적으로 할당되어 있기 때문이다.

    그리고 대입 요청을 받았을 때 대입 대상이 자기 자신이 아닌지도 꼭 점검해야 하는데 A=A 같은 대입문도 일단은 가능해야 하기 때문이다. 이 문장은 자기가 자신의 값을 대입받는 사실상의 NULL문장이지만 고의든 실수든 아니면 코드의 일관성을 위해서건 틀린 문법은 아니므로 지원하는 것이 옳다. 자기 자신이 대입될 때는 아무 것도 하지 않고 자신을 리턴하기만 하면 된다. 만약 이 조건문을 빼 버리면 delete [] Name에 의해 자신의 버퍼를 먼저 정리해 버리고 정리된 버퍼의 내용을 다시 복사하려고 들기 때문에 객체의 내용이 제대로 유지되지 않을 것이다.

    대입 후 리턴되는 값

    대입 연산자의 리턴 타입이 Person &인 이유는 A=B=C식의 연쇄적 대입이 가능해야 하기 때문이다. 대입만이 목적이라면 void형으로 선언해도 상관없겠지만 기본 타입에서 가능한 모든 연산이 객체에서도 가능해야 하므로 가급적 똑같이 동작하도록 만들어야 한다. 대입 연산자가 대입된 결과값을 리턴하기 때문에 연쇄적인 대입이 가능하다.

    이때 리턴되는 객체가 상수일 필요는 없는데 대입 후 리턴되는 객체를 바로 사용할 수도 있고 변경할 수도 있다. (Young=Boy).OutPerson(); 식으로 대입받은 좌변 객체에 대해 멤버 함수를 호출할 수 있다. 설사 이 멤버 함수가 객체의 상태를 변경하는 비상수 함수라도 말이다. 기본 타입도 대입 연산자에 의해 리턴되는 것은 좌변값인데 다음 테스트 코드를 통해 확인해 보자.

     

         int i=1,j=2;

         (i=j)=3;

         printf("%d,%d\n",i,j);

     

    i=j 대입문에 의해 i에 2가 대입되고 i 자체가 리턴된다. 이때 리턴되는 레퍼런스는 좌변값이므로  바로 3을 대입할 수 있다. 출력되는 결과는 3,2가 된다. 실제로 이런 식은 잘 쓰이지도 않고 실용성도 없지만 어쨌든 클래스는 기본 타입과 같아야 하므로 기본 타입들이 하는 짓은 다 할 수 있어야 한다.

    올바른 디폴트 생성자

    Person3 예제는 디폴트 생성자를 정의하고 있으므로 Person Young; 선언문으로 일단 객체를 먼저 만들어 놓고 다른 객체의 값을 대입받아도 상관없다. 디폴트 생성자는 받아들이는 인수가 없으므로 멤버들을 NULL, 0, FALSE로 초기화하여 쓰레기를 치우는 것이 통상적인 임무이지만 동적 할당을 하는 클래스의 경우 포인터를 NULL로 초기화해서는 안된다. 왜 그런지 다음 테스트 코드를 실행해 보자.

     

         Person() { Name=NULL;Age=0; }

     

         Person Boy;

         Person Young=Boy;

     

    디폴트 생성자가 쓰레기를 치우고 있으므로 인수없이 객체를 생성할 수 있다. 그러나 이렇게 만들어진 객체를 사용할 때 여기저기서 문제가 생긴다. 위 테스트 코드는 복사 생성자를 호출하는데 복사 생성자의 본체에서 strlen 함수로 Other.Name의 길이를 구하고 있다. 0번지는 허가되지 않은 영역이므로 이 번지를 읽기만 해도 당장 다운되어 버린다. 복사 생성자가 쓰레기만 치운 객체를 전달받아도 죽지 않으려면 예외 처리 코드가 더 작성되어야 한다.

     

         Person(const Person &Other) {

              if (Other.Name == NULL) {

                  Name=NULL;

              } else {

                  Name=new char[strlen(Other.Name)+1];

                  strcpy(Name,Other.Name);

              }

              Age=Other.Age;

         }

     

    초기식의 객체가 NULL 포인터를 가리키면 새로 선언되는 객체도 같이 NULL포인터를 가지도록 해야 한다. 복사 생성자뿐만 아니라 대입 연산자, Name을 참조하는 모든 멤버 함수에서 Name이 NULL인 경우를 일일이 예외 처리해야 하는 것이다. 이렇게 하는 것이 귀찮고 비효율적이기 때문에 디폴트 생성자가 포인터를 초기화할 때는 비록 1바이트라도 할당하여 Name이 NULL이 되지 않도록 하는 것이 좋다. 비록 1바이트에 빈 문자열밖에 들어 있지 않지만 이 메모리도 동적으로 할당한 것이므로 읽을 수 있다.

    Person3의 디폴트 생성자가 할당하는 1바이트는 자리만 지키는 플레이스 홀더(PlaceHolder) 역할을 한다. 아무 짝에도 쓸모없는 것 같지만 Name이 반드시 동적 할당된 메모리임을 보장하여 이 버퍼를 참조하는 모든 코드를 정규화시키는 효과가 있다. 모든 멤버 함수는 Name의 길이가 얼마이든지 무조건 할당되어 있다는 가정하에 Name을 안심하고 액세스할 수 있다.

    동적 할당 클래스의 조건

    이 예제에서 보다시피 초기화와 대입은 여러 모로 다르다는 것을 알 수 있다. 초기화는 객체를 위한 메모리를 할당할 때 이 공간을 어떻게 채울 것인가를 지정하며 일회적인데 비해 대입은 실행중에 같은 타입인 다른 객체의 사본을 작성하며 회수에 제한이 없다. 대입이 초기화보다는 훨씬 더 복잡하고 비용도 많이 든다. 그래서 컴파일러는 복사 생성자와 대입 연산자를 구분해서 호출하며 따라서 우리는 둘 다 만들어야 한다. class A=B; 선언문을 디폴트 생성자로 A를 먼저 만든 후 B를 대입하는 것으로 처리할 경우 속도가 훨씬 더 늦어질 것이다. 실제로 구형 컴파일러는 이런 식으로 초기화를 구현했었다.

    Time이나 Complex 클래스는 복사 생성자가 없어도 선언할 때 다른 객체로 초기화할 수 있으며 대입 연산자를 굳이 정의하지 않아도 객체끼리 안심하고 대입할 수 있다. 왜냐하면 값만을 가지는 클래스는 컴파일러가 만들어 주는 디폴트 복사 생성자, 디폴트 대입 연산자만으로도 충분히 잘 동작하기 때문이다. 이에 비해 Person 클래스는 동적으로 할당하는 메모리가 있기 때문에 여러 모로 관리해야 할 것들이 많은데 최소한 다음과 같은 함수들이 있어야 한다.

     

    함수

    설명

    생성자

    생성될  메모리를 할당한다.

    파괴자

    사용하던 메모리를 반납한다.

    복사 생성자

    초기화될  별도의 메모리를 할당한다.

    대입 연산자

    사용하던 메모리를 해제하고 대입받는 객체에 맞게 다시 할당한다.

     

    이 중 하나라도 빠지거나 생략되면 Person 클래스는 제대로 동작하지 않는다. 생성자는 초기화라는 중요한 임무를 가지므로 꼭 동적 할당을 하지 않더라도 대부분의 클래스에 필수적이다. 나머지 셋은 생성자에서 동적 할당이나 그와 유사한 효과의 동작을 할 때 꼭 필요한데 셋 중 하나가 필요하다면 나머지 둘도 마찬가지로 필요하다. 그래서 이 셋은 같이 뭉쳐서 다니는 특징이 있으며 흔히 삼총사라고 부른다.

    Person3 예제의 Person 클래스는 비로소 완벽해졌으며 선언과 동시에 초기화, 실행중 대입 등이 가능해져 기본 타입과 동등한 자격을 가지게 되었다. 그러나 상속을 하지 않을 경우에만 완벽하며 상속할 경우 파괴자가 가상 함수여야 한다는 조건이 하나 더 추가된다. 이 예에서 동적으로 할당되는 메모리란 클래스 동작에 꼭 필요한 어떤 자원의 비유에 해당한다. 예를 들어 하드웨어 장치를 열어야 하거나 네트워크 접속, DB 연결, 권한 획득 등이 필요한 클래스는 모두 비슷한 법칙이 적용된다. 아무튼 멤버를 그대로 복사해서는 똑같은 객체를 만들 수 없는 모든 클래스에는 이런 함수들이 필요하다.

    복합 대입 연산자

    이번에는 대입 연산자와 유사한 복합 대입 연산자를 오버로딩해 보자. 복합 대입 연산자는 대입과 비슷한 동작을 하기는 하지만 아예 다른 연산자이므로 필요할 경우 따로 정의해야 한다. 예를 들어 Time 클래스에 operator + 연산자를 오버로딩했다고 해서 operator += 까지 같이 정의되는 것은 아니다. 다음은 += 복합 대입 연산자의 오버로딩 예이다.

     

      : OpPlusEqual

    #include <Turboc.h>

     

    class Time

    {

    private:

         int hour,min,sec;

     

    public:

         Time() { }

         Time(int h, int m, int s) { hour=h; min=m; sec=s; }

         void OutTime() {

              printf("%d:%d:%d\n",hour,min,sec);

         }

         Time &operator +=(int s) {

              sec += s;

              min += sec/60;

              sec %= 60;

              hour += min/60;

              min %= 60;

              return *this;

         }

    };

     

    void main()

    {

         Time A(1,1,1);

     

         A+=62;

         A.OutTime();

    }

     

    + 연산자와 다른 점은 호출한 객체를 직접 변경시키기 때문에 const가 아니라는 점, 그리고 자기 자신이 피연산자이므로 임시 객체를 필요로 하지 않는다는 점 정도이다. A+=62 연산문에 의해 A가 가진 시간에 62초를 더한 값이 A에 다시 대입된다. 사용자는 + 연산이 가능하면 +=연산도 가능하다고 기대하므로 가급적이면 두 연산자를 같이 제공하는 것이 좋다. 이 경우 +=을 먼저 정의해 놓고 + 연산자는 이 함수를 호출하는 것이 효율적이다.

     

    Time operator +(int s) {

         Time R=*this;

         R+=s;

         return R;

    }

     

    +=에 정수를 더하는 연산이 먼저 정의되어 있으므로 +는 임시 객체에 += 연산한 결과를 값으로 리턴하기만 하면 된다. 뿐만 아니라 덧셈의 규칙이 변경되더라도 +=의 코드만 수정하면 되므로 코드를 유지하기도 훨씬 더 쉽다.

    복사 생성 및 대입 금지

    클래스는 일종의 타입이므로 기본 타입과 완전히 동일해질 수 있는 모든 문법이 제공된다. 선언, 초기화, 복사, 대입, 연산 등등 int가 할 수 있는 모든 동작을 다 할 수 있다. 그러나 경우에 따라서는 이런 것이 어울리지 않거나 그래서는 안되는 클래스들도 있다. 예를 들자면 하드웨어를 직접적으로 제어하거나 유일한 자원을 관리하는 객체를 들 수 있는데 하나만 가지고도 충분히 원하는 동작을 모두 할 수 있으므로 굳이 둘을 만들 필요가 없다.

    이런 예는 멀리서 찾을 것도 없이 표준 입출력 스트림 객체인 cin, cout을 보면 된다. 이 객체 한쌍으로 화면에 원하는 모든 출력을 할 수 있고 키보드로 입력을 받을 수 있는데 cin, cout이 두 개씩 있을 필요가 없지 않은가? 어떤 경우에는 동일한 타입의 객체가 두 개 있을 경우 혼선이 빚어지기도 하고 서로 간섭하여 오동작하거나 데드락에 걸리는 부작용도 있다. 이런 클래스들은 허가되지 않는 연산을 적절히 막아야 하는데 금지할 필요가 있는 대표적인 연산이 복사 생성과 대입이다.

    복사 생성과 대입을 못하는 클래스를 만드는 방법은 생각보다 쉽다. 복사 생성자와 대입 연산자를 선언하되 둘 다 private 영역에 두는 것이다. 아예 정의하지 않으면 컴파일러가 디폴트를 만드므로 반드시 private영역에 직접 선언해야 한다. 어차피 호출되지 않을 함수들이므로 본체의 내용은 작성하지 않아도 상관없다. Person 클래스는 복사, 대입이 모두 가능한 경우이긴 하지만 금지해야 한다고 가정하고 위 예제를 대상으로 이 동작들을 금지시켜 보자.

     

    class Person

    {

    private:

         char *Name;

         int Age;

         Person(const Person &Other);

         Person &operator =(const Person &Other);

         ....

     

    이렇게 해 놓으면 Person Young=Boy; 같은 선언문이나 Girl=Boy; 같은 대입문이 실행될 때 컴파일러가 복사 생성자나 대입 연산자를 호출하려고 할 것이다. 객체를 선언하는 곳은 객체의 외부이므로 private 멤버를 호출할 수 없으며 컴파일 중에 이 동작이 허가되지 않는다는 것을 알 수 있다. 실행중에 문제를 일으키는 것보다 컴파일할 때 이 동작은 금지되었음을 확실히 알리는 것이 바람직하다. 좀 더 적극적으로 에러 내용을 상세하게 알리고 싶을 때는 이 둘을 public 영역에 두되 assert문을 작성해 놓는 방법을 쓸 수 있다.

    두 함수를 private 영역에 둘 때 본체 내용은 아예 작성하지 않는 것이 좋다. 왜냐하면 외부에서 이 함수를 호출하는 것은 컴파일러가 컴파일 중에 막아 주지만 클래스 내부의 멤버 함수나 프렌드 함수에서는 여전히 이 함수를 호출할 수 있기 때문이다. 함수를 선언만 해 놓고 본체를 정의하지 않더라도 이 함수가 호출되기 전에는 링커가 본체를 찾지 않으므로 아무 이상이 없다. 만약 정의되지도 않는 함수를 호출하려고 하면 컴파일은 무사히 되지만 링크할 때 에러로 처리되므로 이 동작이 불가능하다는 것을 알 수 있다. 외부에서 불가능한 동작을 시도하면 컴파일러가 막아주고 내부에서 엉뚱한 짓을 하려면 링커가 막아준다. C++의 객체는 이런 식으로 실수든 고의든 허가되지 않는 위험한 연산을 스스로 방어하도록 작성되어야 한다.

    복사 생성자, 대입 연산자 작성 규칙은 나름대로 복잡해서 이해는 되더라도 실무에서 직접 작성하기는 쉽지가 않다. 개념적인 이해는 꼭 해 두고 실제 코드를 작성할 때는 Person3 예제에서 코드를 복사한 후 원하는 부분만 수정하는 것이 편리하다. Person3 예제의 복사 생성자, 대입 연산자는 모든 상황에 대해 잘 작동하도록 만든 모범 답안이다.

     

    반응형
    반응형


    <헤더파일>

    ...

    class Nice

    {

    private:

       static int totalObject;

    // static int totalObject = 0;  (안 돼!)

    public:

        Nice();

        ~Nice();

        void ImAMethod();

    }

    ...

     

    <구현파일>

    ...

    int Nice::totalObject = 0; // static 멤버 변수의 초기화

     

    Nice::Nice()

    {

        // totalObject = 0;  (안 돼!)

    ...







    // 정적 변수

     

    Num은 클래스의 멤버이면서 클래스로부터 생성되는 모든 객체가 공유하는 변수여야 한다. 이것이 바로 정적 멤버 변수의 정의이며 이 문제를 풀 수 있는 유일한 해결책이다.

     

    //h. 파일

    class Count

    {

    private:

         int Value;

        static int Num;

    public:

         Count() { Num++; }

         ~Count() { Num--; }

         void OutNum() {

              printf("현재 객체 개수 = %d\n",Num);

         }

    };

     

    //cpp 파일

    int Count::Num=0;

    .....

     

    헤더 파일의 클래스 선언부에 정적 멤버 변수에 대한 내부 선언이 있고 구현 파일에 정적 멤버 변수에 대한 외부 정의 및 초기값 지정문이 온다. 이게 관례이다. 만약 헤더 파일에 외부 정의를 둔다면 헤더 파일이 두 번 인클루드될 때 이중 정의되므로 에러로 처리될 것이다.

     

    이렇게 선언하면 Num은 Count 클래스에 소속되며 외부 정의에서 지정한 초기값으로 딱 한 번만 초기화된다. Count형의 객체 A,B,C가 생성되었다면 각 객체는 자신의 고유한 멤버 Value를 개별적으로 가지며 정적 멤버 변수 Num은 모든 객체가 공유한다. 그래서 각 객체의 생성자에서 증가, 파괴자에서 감소하는 대상은 공유된 변수 Num이며 한 변수값을 모든 객체가 같이 관리하므로 Num은 생성된 객체의 정확한 개수를 유지할 수 있다.

     

    정적 멤버 변수는 객체와 논리적으로 연결되어 있지만 객체 내부에 있지는 않다. 정적 멤버 변수를 소유하는 주체는 객체가 아니라 클래스이다. 그래서 객체 크기에 정적 멤버의 크기는 포함되지 않으며 sizeof(C) = sizeof(Count)는 객체의 고유 멤버 Value의 크기값인 4가 된다.

     

    단 외부에서 정적 멤버 변수를 정의할 때는 예외적으로 액세스 속성에 상관없이 초기값을 줄 수 있다. 초기식은 대입과는 다르므로 액세스 속성의 영향을 받지 않는다. 정적 멤버 변수를 외부에서도 참조할 수 있도록 공개하려면 클래스 선언부의 public영역에 선언하면 된다. 외부에서 정적 멤버를 액세스할 때는 반드시 소속을 밝혀야 하는데 두 가지 방법으로 소속을 밝힐 수 있다.

     

    Count C;

    Count::Num=3;            // 클래스 소속

    C.Num++;                        // 객체 소속

     

    원한다면 C.Num처럼 객체.멤버 식으로 객체의 소속인 것처럼 표현할 수도 있다. 이 때 C객체의 이름은 별다른 의미는 없으며 C객체가 소속된 클래스를 밝히는 역할만 한다. 정적 멤버에 대해 객체의 소속으로 액세스하는 것은 일단 가능하지만 일반적이지 않으며 바람직하지도 않다. 정적 멤버는 논리적으로 클래스 소속이므로 가급적이면 클래스::멤버 식으로 액세스하는 것이 합당하다.

     

    //정적 함수

     

    정적 멤버 함수의 개념도 정적 멤버 변수의 경우와 비슷하다. 객체와 직접적으로 연관된다기보다는 클래스와 연관되며 생성된 객체가 하나도 없더라도 클래스의 이름만으로 호출할 수 있다. 일반 멤버 함수는 객체를 먼저 생성한 후 obj.func() 형식으로 호출한 객체에 대해 어떤 작업을 한다. 이에 비해 정적 멤버 함수는 Class::func() 형식으로 호출하며 클래스 전체에 대한 전반적인 작업을 한다. 주로 정적 멤버 변수를 조작하거나 이 클래스에 속한 모든 객체를 위한 어떤 처리를 한다.

    정적 멤버 함수를 선언하는 방법은 정적 멤버 변수와 동일하다. 클래스 선언부의 함수 원형앞에 static이라는 키워드만 붙이면 된다. 정적 멤버 함수의 본체는 클래스 선언부에 인라인 형식으로 작성할 수도 있고 아니면 외부에 따로 정의할 수도 있는데 외부에 작성할 때 static 키워드는 생략한다.

     

    #include <Turboc.h>

     

    class Count

    {

    private:

         int Value;

         static int Num;

     

    public:

         Count() { Num++; }

         ~Count() { Num--; }

         static void InitNum() {

              Num=0;

         }

         static void OutNum() {

              printf("현재 객체 개수 = %d\n",Num);

         }

    };

    int Count::Num;

     

    void main()

    {

         Count::InitNum();

         Count::OutNum();

         Count C,*pC;

         C.OutNum();

         pC=new Count;

         pC->OutNum();

         delete pC;

         pC->OutNum();

         printf("%d",sizeof(C));

    }

     

    InitNum은 정적 멤버 함수이므로 Count 클래스의 객체가 전혀 없는 상태에서도 호출될 수 있다. main에서 Count::InitNum()을 먼저 호출하여 Num을 0으로 초기화하였다. 변수를 초기화하는 별도의 함수를 만들었으므로 원한다면 실행중에 언제든지 이 함수를 호출하여 Num을 0으로 리셋할 수도 있다.

     

    C 객체를 생성한 후 C.OutNum()을 호출하면 1이 출력되고 pC객체를 동적 생성한 후 pC->OutNum()을 호출하면 2가 출력된다. 이 두 호출의 예처럼 정적 멤버 함수를 객체의 이름으로 호출할 수도 있지만 이때 객체의 이름은 아무런 의미가 없으며 컴파일러는 객체가 소속된 클래스의 정보만 사용한다. C.OutNum(), pC->OutNum(); 이라는 표현을 허용할 뿐이지 이 호출은 실제로 Count::OutNum()으로 컴파일된다는 얘기다.

     

    그래서 delete pC;로 pC 객체를 해제한 후에도 pC->OutNum()이라는 호출이 정상적으로 동작한다. 컴파일러는 pC에 실제로 객체가 생성되어 있는지를 볼 필요도 없으며 pC가 Count *형이라는 것만 참조할 뿐이다. 심지어 main의 4번째 줄에 pC가 할당되기도 전인 C.OutNum()을 pC->OutNum()으로 바꿔도 잘 동작한다. 이걸 보면 컴파일러가 포인터의 타입만으로 호출할 함수를 결정한다는 것을 알 수 있다.

     

    주의)

    정적 멤버 함수는 특정한 객체에 의해 호출되는 것이 아니므로 숨겨진 인수 this가 전달되지 않는다. 클래스에 대한 작업을 하기 때문에 어떤 객체가 자신을 호출했는지 구분할 필요가 없으며 따라서 호출한 객체에 대한 정보도 필요가 없는 것이다. 그래서 정적 멤버 함수는 정적 멤버만 액세스할 수 있으며 일반 멤버(비정적 멤버)는 참조할 수 없다. 왜냐하면 일반 멤버 앞에는 암시적으로 this->가 붙는데 정적 멤버 함수는 this를 전달받지 않기 때문이다. 정적 멤버 함수인 InitNum에서 비정적 멤버인 Value를 참조하는 것은 불가능하다.

     

    static void InitNum() {

         Num=0;

         Value=5;

    }

     

    이 코드를 컴파일하면 정적 멤버 함수에서 Value를 불법으로 참조했다는 에러 메시지가 출력된다. InitNum의 본체에서 Value를 칭하면 누구의 Value인지를 판단할 수 없다. 또한 정적 멤버 함수는 생성된 객체가 전혀 없어도 호출할 수 있는데 이때 Value는 아예 존재하지도 않는다. 비정적 멤버 함수도 호출할 수 없으며 오로지 정적 멤버만 참조할 수 있다.

    [출처] Static 멤버 변수의 초기화|작성자 

    반응형
    반응형

    _MSC_VER 은 Visual C++ 의 컴파일러 버전을 나타내는 매크로 상수임

    1000 : Visual C++ 4.x
    1100 : Visual C++ 5
    1200 : Visual C++ 6
    1300 : Visual C++ .NET
    1310 : Visual C++ .NET 2003
    1400 : Visual C++ .NET 2005
    1500 : Visual C++ .NET 2008

    /* stdafx.h */

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000

    반응형
    반응형

    상수란 말씀하신 것처럼 값을 가지는 변수이나 그 값을 바꿀 수 없는 변수입니다.

    즉 한번 메모리에 변수를 지정하고 그 변수에 값을 초기화하고 난 그 이후에는 값을 바꿀 수 없는

    변수를 상수라고 일컫습니다. 다음과 같은 경우, PI가 상수가 되겠죠.

    const float PI=3.14f; // C

    public static final float PI=3.14f; // Java

    반면 리터럴은 이러한 변수 및 상수에 저장되는 값 자체를 일컫습니다.

    정수 리터럴, 실수 리터럴, 문자열 리터럴 이런 것들이 프로그래밍 언어의 한 요소로서

    리터럴이라고 불리는 겁니다.

    정수 리터럴 ---> 10, 1, 1000 등등
    실수 리터럴 ---> 10.1, 10e3 등등
    문자열 리터럴 ---> "System" "Exit" 등등

    쉽게 얘기하면 변수나 상수는 메모리에 할당된 공간(내지는 그 이름, 사실 이보다는 바인딩이라고 해서

    좀더 복잡한 얘기를 해야하지만 질문 내용과 무관하므로 생략하도록 하겠습니다.)이라면 리터럴은

    이 공간에 저장되는 값이라고 할 수 있습니다.

    ===========================================================================================

    바로 값이 되는 상수를 말하는 거죠.

    보통 프로그래밍을 할때 변수라는 걸 사용하는데.
    이 변수라는건 여러가지가 결합된 요소로 볼 수 있습니다.
    변수명, 주소, 공간, 형식이란 걸로 결합이 됩니다.
    변수명이란 식별자로 해당 메모리 주소를 참조해서 지정된 형식으로 데이타를 읽거나 쓸 수 있는것이죠.
    이에 반해서 상수란건 동일하게 구성이 되지만 쓰기연산이 금지된 겁니다.

    읽기만 가능하고 쓰기가 불가능해서 항상 같은 값을 유지하자는 거죠.

    리터럴은 다시 변수명이란게 사라진겁니다.

    이름같은 식별자를 통하지 않고. 바로 값이 되는 상수들을 특별히 리터럴이라고 합니다.

    숫자 상수 10이나 문자 상수 'A' 문자열 상수 'ABC' 이런것들이 리터럴이죠.

    C에서는 특별히 '\xx'같은 형태를 이스케이트 캐릭터, 확장열이라고 따로 문자상수를 정의해서 사용하기도 합니다

    반응형
    반응형

    http://www.winapi.co.kr/:

    아래 3개는 모두 동일

    const int Day=24;

    int const Day=24;

    const Day=24; //int 를 안쓰면 default 로 int 가 선언되어진다

    int 의 경우 const 순서 상관없이 변수값이 const 가 되지만 포인터의 경우에는 차이가 있다

    15-1-나.포인터와 const

    const를 포인터와 함께 사용하면 효과가 조금 달라진다. 다음 예제는 포인터와 const의 관계를 실험해 보기 위해 작성했는데 컴파일해 보면 몇 군데서 에러가 발생할 것이다.

    : ConstPointer

    #include <Turboc.h>

    void main()

    {

    int ar[5]={1,2,3,4,5};

    int *pi1=&ar[0];

    pi1++; // 포인터가 다른 대상체를 가리킬 수 있다.

    *pi1=0; // 대상체를 변경할 수 있다.

    const int *pi2=&ar[0];

    pi2++; // 포인터가 다른 대상체를 가리킬 수 있다.

    *pi2=0; // 에러 : 대상체가 상수이므로 변경할 수 없다.

    int * const pi3=&ar[0];

    pi3++; // 에러 : 포인터가 다른 대상체를 가리킬 수 없다.

    *pi3=0; // 대상체는 변경할 수 있다.

    const int * const pi4=&ar[0];

    pi4++; // 에러 : 포인터가 다른 대상체를 가리킬 수 없다.

    *pi4=0; // 에러 : 대상체가 상수이므로 변경할 수 없다.

    }

    반응형
    반응형

    http://www.gpgstudy.../viewtopic.php?t=566
    http://www.gpgstudy.../viewtopic.php?t=627

    http://www.gpgstudy.../viewtopic.php?t=860

    * get - 가장 흔히 쓰이며 의미하는 범위도 가장 넓다. GetDC(), getInstance(), getPosition() 등등. get은 아래에 나열된 다른 모든 돌려주는 동사들을 포괄한다. 그러나, get의 용도를 최대한 한정시킨다면 주로 ‘이미 계산되어 있는 객체의 속성 또는 시스템의 특정 상태를 돌려주며 상태, 시스템을 변경하거나 어떠한 복잡한 처리를 하지 않는’ 함수에 쓰는 것이 좋다고 본다.


    * retrieve - get에 비해 이것은 반환할 결과를 찾기 위한 처리(주로 검색)가 일어남을 암시한다. 필요하다면 이 동사는 search 같은 실제 수행 동사와 get으로 나눌 수 있다.

    예:

    aCheapestApple = anAppleBox.retrieveApple(CHEAPEST);

    =>

    anAppleBox.search(CHEAPEST);
    aCheapestApple = anAppleBox.getSearchResult();


    * acquire - 제한된 자원을 독점적으로 확보한다는 뜻이 있다. 예를 들어 주어진 윈도우 또는 시스템 전체에 DC가 하나이며 GetDC()로 얻은 DC를 ReleaseDC()로 풀기 전까지는 혼자만 사용해야 하는 상황이라고 하자. 그런 경우라면 GetDC() 보다는 AcquireDC()가 더 적합하다. 반환값은 확보한 객체 자체나 포인터, 핸들 등이다. 반대말은 release나 unacquire(get에 대해 release를 사용한다면 acquire에 대해서는 unacquire를 사용해서 구분해 주는 것이 좋을 것임).

    예:

    AudioManager AudioMgr = new DxAudioManagerImpl();
    ...
    AudioDevice* anAudioDevice = AudioMgr.acquireDevice();
    ...
    AudioMgr.unacquireDevice(anAudioDevice);

    * fetch - 돌려준 값을 담고 있는 컨테이너의 내부 상태가 변함을 의미한다. 주어진 컨테이너 안의 어떤 값을 가져오면 현재 값의 위치를 가리키는 포인터가 다음 값으로 이동하게 되는 경우에 fetch가 쓰인다. 데이터베이스 API에서 흔히 볼 수 있다. 스택의 pop과도 비슷하다. 또한 파일 시스템의 많은 함수들이 이러한 fetch 식으로 작동한다. 이는 moveNext와 get으로 분리될 수 있다.

    예:

    UnitList.reset();
    pUnit1 = UnitList.fetch();
    pUnit2 = UnitList.fetch();
    =>
    UnitList.moveFirst();
    pUnit1 = UnitList.getUnit();
    UnitList.moveNext();
    pUnit2 = UnitList.getUnit();

    * be 동사와 조동사 - 이들은 어떠한 상태 또는 여부를 뜻하는 부울 값을 돌려줄 때 주로 쓰인다.

    예:
    while ( isDone ) {
    if ( currentUnit.hasWeapon() ) ....
    ....

    }

    2. 객체의 상태를 변경하는 함수에 주로 쓰이는 동사들

    객체 또는 현재 범위의 어떤 값을 변경하는 함수들에 가장 흔히 쓰이는 동사는 set이다. 이런 동사들은 반환값이 없거나 또는 함수의 성공 여부를 알려주는 반환값을 가진다.

    * set - 가장 흔하게 쓰인다. 설명이 필요없겠지만 굳이 부연 설명을 하자면, get의 반대물이라는 차원에서, 상태의 변경에 복잡한 처리가 일어나지 않는 비교적 단순한 코드로 된 함수에 적합하다.

    그래픽 시스템

    display - 실제의 시각적 변화를 일으킨다는 의미가 있다(버퍼 스왑 등 실제 비디오 메모리 갱신의 차원에서). 또는 아래의 동사들을 포괄하는 고차원 함수에도 쓰인다(특정 그래픽 API에 국한되지 않는...)

    render - display와 비슷하나 내부 버퍼 메모리의 갱신만 의미할 수도 있다. 즉 render 후 상황에 따라 display가 수행되는 등. render부터는 특정 그래픽 API에 국한된 함수에 가까와진다. 목적어를 가질 수 있다(renderScene, renderBackground 등)

    draw - 개별 데이터나 개별 개체를 그린다는 의미로 많이 쓰인다. Model.draw()나 drawText() 등. render 안에서 일어나는 경우가 많다.

    put - 주로 2D에서 점이나 스프라이트를 ‘찍는다’는 의미로 쓰인다. 실제의 시각적 변화보다는 내부 메모리에서의 변화일 가능성이 크다. 3D에서는 별로 쓰이지 않음. 목적어를 가지는 경우가 많다(putPixel, putSprite 등)

    flush - 그래픽이나 장면 데이터를 스트림으로 간주할 때 쓰인다. 지금까지 명령 버퍼 또는 프레임 버퍼에 쌓여 있는 데이터를 실제 장치로 밀어 내보낸다는 의미를 가진다. 






    올리기동사에 추가해서 올려짐: 2002-09-06 09:49
    인용과 함께 답변 이 게시물을 del.icio.us에 추가

    가끔가다가..
    명사나 형용사로 함수명을 지어야 할때가 있습니다.
    무엇무엇과 같은것을...해라..
    음 단순한 동사로 했다가는 의미가 애매해지는 경우를 말하는것이죠..
    그럴때는 저의 경우는 Do를 앞에 써줍니다.
    행동을 해줘야 한다는걸 강조해 주는거죠..
    벽깨는 동작을 해라.. 라고 할때 BreakTheWall을 하면..
    벽을깨는거지만.
    DoMotionBreakingTheWall 이라고 하면 벽깨는 동작을 해라 라고 하게 됩니다.

    혹은.. bool값을 반환하는 단순상태의 경우는 Is나 Does를 앞에 써줍니다.
    이런 펑션을 If문 안에 써주면 주석을 쓰지않아도 의미가 명료해 집니다.
    GetBound?? 이런건 이름자체가 잘못된거지만.. bool을 이렇게 쓰면 웃길때가 있다고 치고
    IsInsideBound 는 명료하게 참거짓을 쿼리하는거라는 인식을 하게 하죠..
    음... 이건 제 습관이라서... 많은 공격 바랍니다.







    류광



    가입: 2001년 7월 25일
    올린 글: 3452
    소속: GPGstudy


    타 사이트 ID(?):
    docbook.kr::류광
     
    올려짐: 2002-09-06 17:18
    인용과 함께 답변 이 게시물을 del.icio.us에 추가


    아 좋은 지적입니다.. 동사들을 이야기하면서 뭔가 빠진 듯한 느낌이 들었는데 Do 류의 동사들이었네요. do, excute, run, process 등... 이들의 미묘한 차이에 대해서도 조만간 정리해야 겠습니다...

    한 가지.. Do의 경우 또 다른 용법은 Do 다음에 실제 동사가 오는 형태입니다. 이런 것은 처리의 위임이나 전처리 후 실제 처리 같은 경우에 많이 보이더군요. 예를 들어

    코드:
    // 다른 어떤 곳에서
    obj->OnDraw(); // 또는 그냥 obj->Draw()
    ...
    void SomeObject::OnDraw() // 역시 또는 그냥 Draw()
    {
    ...// 전처리
    // 실제 처리
    DoDraw();
    }


    이런 예에서 Do는 '이제 진짜로 하자~'라는 강조로 쓰이는 것 같습니다..






    반응형
    반응형


    보통은 자식 클래스가 자신의 부모 클래스의 생성자만 호출 가능하고, 할아버지 클래스의 생성자를

    호출 할 수 없지만, 가상 기반 클래스 에서는 예외가 있습니다...

    즉, 손자 클래스가 자신의 부모보다도 상위인 할아버지 클래스의 생성자를 호출 할 수

    있다는 점이죠...

    또한, 이 경우에 있어서는 중간 클래스를 의미하는 부모 클래스(class XY, class XZ) 에서는 비록...

    최상위 클래스의 생성자를 호출하는 코드인 CPointX(a) 가 XY, XZ 클래스의 생성자 초기화 리스트에

    있지만, 손자 클래스인 XYZ 클래스의 생성자 초기화 리스트 부분에서...단 "한 번만" 호출된다는

    점 입니다...

    브레이크 포인트를 걸어서 코드 흐름을 추적해 보면 알겠지만, 위 코드에선 CPointX(a) 부분이 한 번 밖에

    호출되지 않고, 이를 담당하는 것은 손자 클래스인 XYZ 클래스 입니다...

    정리하면,,,가상 기반 상속에서는 손자 클래스에서 할아버지 클래스의 생성자를 직접 호출한다는

    점이고, 중간 클래스인 부모 클래스에서는 비록, 그 상위 클래스의 생성자를 호출하는 코드가

    있다고 하더라도 호출되지 않는다는 점 입니다...

    또한...가상 상속을 사용하는 이유는 할아버지 클래스인 X 클래스를 상속받는 XY, XZ 클래스를 XYZ 라는

    클래스가 상속받기 때문에...XYZ 입장에서는 X 클래스의 멤버가 어디에서 온 멤버인지 모호하기 때문에

    이 가상상속을 사용하여, 모호함을 없애는 것입니다...

    X X

    | |

    XY XZ

    XY, XZ 를 상속받는 XYZ 클래스는 X의 멤버를 참조할 때 XY 에서 온 X인지, XZ 에서 온 X인지

    모호함으로 인해 판단하기 어렵다는 점입니다...

    그래서...이를 해결하고자 가상상속을 사용하는 것이고, 손자 클래스가 할아버지 클래스의 생성을

    직접 담당하는 것입니다...

    코드를 다음과 같이 수정 합니다...

    class CPointXY : virtual public CPointX{

    .....

    // CPointX(a) 생성자는 여기에서 호출되지 않는다.

    CPointXY(int a, int b) : CPointX(a), y(b)

    };

    class CPointXZ : virtual public CPointX{

    .....

    // CPointX(a) 생성자는 여기에서 호출되지 않는다.

    CPointXZ(int a, int c) : CPointX(a),z(c)

    };

    class CPointXYZ : public CPointXY, public CPointXZ{

    ...

    // 할아버지 클래스의 생성자를 직접 호출한다

    // 만약, CPointX(a) 를 명시적으로 호출하는 코드가 없다면 할아버지 클래스인

    // CPointX 클래스에 인자를 가지지 않는 디폴트 생성자가 있어야 합니다...

    CPointXYZ(int a, int b, int c) : CPointX(a), CPointXY(a,b), CPointXZ(a,c), xyz(0)

    ...

    };





    출처 : 

    http://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040101&docId=76771879&qb=7KaJLCDshpDsnpAg7YG0656Y7Iqk6rCAIOyekOyLoOydmCDrtoDrqqjrs7Tri6Trj4Qg7IOB7JyE7J24IO2VoOyVhOuyhOyngCDtgbTrnpjsiqTsnZgg7IOd7ISx7J6Q66W8IO2YuOy2nCDtlaAg7IiYIOyeiOuLpOuKlCDsoJDsnbTso6AuLi4=&enc=utf8&section=kin&rank=1&search_sort=0&spq=0&pid=R5vBMF5Y7uKsscvCnRhssc--453720&sid=UJDjOsm@kFAAAHDkOiE

    반응형
    반응형

    출처 : http://kin.naver.com/browse/db_detail.php?d1id=1&dir_id=10104&docid=285334

    void 형변환에 관하여 %p


    #include

    int multi[2][4];

    int main()
    {
    printf("\nmulti = %p",(void *)multi); /* ----------- 1
    printf("\nmulti[0] = %p",(void *)&multi[0]); /* ---------- 2
    printf("\nmulti[0] = %p",(void *)multi[0]); /* ---------- 3
    printf("\n&multi[0][0] = %p\n",(void *)&multi[0][0]); /* ---------- 4
    return 0;
    }

    요기에 보면.. void로 형변환 해주자나여.. 이게 맞는 거는 알겠는데, 왜
    void 형변환을 꼭 해줘야 하는지..

    형변환 안하고, 그냥 %u 쓰구 컴파일하면,

    warning: unsigned int format, pointer arg (arg 2) 이런 경고 메시지가
    뜨더라구여..

    void 포인터가 뭔지는 아는데요..

    가령 1번 줄에서, multi 를 (void *)multi 로 형변환해주면, multi가 가지고
    있는 bit(표현이 맞나?)에

    어떤 변화가 생기는건지 궁금하구, 또 왜 그렇게 형변환을 해줘야 하는지요..

    그것은 %p라는 것은 void* 형을 다룹니다.



    [질문자 평]

    사실, 제가 VC++로 컴파일로 하니.. 에러는 발견되지가 않더군요..

    아마 컴파일러랑 라이브러리의 영향을 받는 warning 증상 같습니다...^^;

    그런데, 조금 좋군요..ㅋㅋㅋ;

    %p는 (void*)를 취급하며, 그것의 주소를 화면의 출력을 하는 구조입니다.

    %u는 그것을 unsigned int 정수형으로 출력을 하라는 것이구요..

    실제 구현상에는 돌아갈때는 아무런 변화가 있을수도 있고, 없을 수도 있습니다.

    int 형은 32 bit인데 char형은 8 bit에다 넣을 려면은 어떻게 해야할까요?

    이중에서 해당하는 하위 8 bit를 넣어야 겠죠.. 이런식의 변화는 있을수 있습니다.

    또 실수형을 정수형으로 넣을경우는? 거기의 맞는 특별한 연산을 해줘야 겠죠?

    물론, (void*)는 영향이 없고, 단순히 시작주소만을 가집니다.. 즉, 모형를 모른다는 것입니다.

    모형의 요소 - 할당바이트, 데이터 저장 형식(실수, 정수, 문자..)

    다시 정리하면은 %p는 (void*)로 받도록 약속을 하였습니다. 그래서 형변환을 해줘야 warning을 뜨지않고, 프로그래머가 의도적으로 한것이구나를 컴파일러가 알아서 거기의 맞는 동작을 하겠죠? 그래서 형변환을 하였습니다.^^;


    그래서 어떠한 포인터를 삭제할때

    struct del{

    void operator()(void * ptr){

    delete ptr;

    }

    };

    이 가능하다

    반응형
    반응형

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <iostream>
    #include <Windows.h>
    #include <math.h>
    using namespace std;


    class c{
    public :
    c(){

    cout<<"c 생성자"<<endl;
    }

    ~c(){

    cout<<"c 소멸자"<<endl;
    }
    };


    class a : public c{
    public:

    a(){

    cout<<"a 생성자"<<endl;
    }
    ~a(){
    cout<<"a 소멸자"<<endl;
    }
    };

    class b{
    public:
    a ai;
    b(){
    cout<<"b 생성자"<<endl;
    }
    ~b(){
    cout<<"b 소멸자"<<endl;
    }
    };

    int main(void)
    {
    /*
    a ai; 의 경우
    생성자는 부모부터 실행되지만
    소멸자는 virtual 처럼 자식 부터 실행된다.
    */

    /*
    b bi;
    b 안에 클래스 멤버가 포함되어 있으면 포함된 생성자가 먼저 호출 된다
    */
    return 0 ;
    }

    반응형
    반응형

     rand() 함수는 난수를 발생하는데 srand(시드값)에 의한 시드값 설정이 없다면

    rand() 자체에서 고정된 시드값으로 난수를 발생하게 된다, 즉 시드값 을 따로 설정하지 않으면

    동일한 시드값으로 rand() 가 계산되어지기 때문에 프로그램이 끝나고 다시 실행하면 동일한 난수들이 나열된다

    내부적으로 고정된 씨드값 =c

    rand(); -> // srand(c); c 라는 상수로 srand 함수가 난수 테이블을 설정하면 rand()함수의 한번 콜이 일어날때

    // 난수테이블의 첫번째 숫자부터 리턴 되는 형식, (난수 테이블이 난수 함수일 수 있다.)

    즉 srand( 시드값 ) 난수테이블을 변경할 수 있는 상수 시드값이 아닌 변경되는 값을 시드값으로 준다면

    rand() 는 변경되는 테이블값의 다른 첫 난수 값을 가져올 수 있을 것이다

    변경되는 시드값은 time(NULL) 로 쓸 수도 있으나 이것은 1초 사이클로 +1 씩증가하는 함수이기 때문에

    1초 사이에는 동일한 난수가 발생할 것이다

    계속 적인 변화를 원한다면 GetTickCount() 같은 함수를 쓰면 되겠다.

    반응형
    반응형

    BSTR IString::ToBSTR()
    {
    BSTR BStr;
    int iWLength;

    iWLength = ::MultiByteToWideChar(CP_ACP, 0, mcpStr, Length(), NULL, NULL);
    BStr = ::SysAllocStringLen(NULL, iWLength);
    ::MultiByteToWideChar(CP_ACP, 0, mcpStr, Length(), BStr, iWLength);

    return BStr;
    }

    반응형
    반응형

    PathFileExists(파일경로가 붙은 파일 이름) 파일이 존재하는지 여부

    if(PathFileExists(PathName.ToChar()))

    PathFileExists(파일경로가 붙은 파일 이름) 파일이 존재하는지 여부를 알려준다

    반응형
    반응형

    http://avangs.info/zbxe/533483

    3-5-나.확장열

    문자 상수는 홑따옴표안에 문자를 써서 표기한다. 문자 Y에 대한 문자 상수는 'Y'다. 이 상수를 키보드로 입력하려면 키보드에서 '를 치고 Y를 치고 '를 치면 된다. 그런데 따옴표안에 직접 입력할 수 없는 문자가 있다. 대표적으로 개행 코드를 들 수 있는데 Enter키를 누르는 즉시 정말로 다음 줄로 내려가 버리기 때문에 따옴표안에 개행 코드를 담아서 표현하는 것은 불가능하다.

    또한 문자 상수를 표현할 때 사용하는 홑 따옴표 구두점도 문자 상수로 바로 표현할 수 없다. ''' 이렇게 쓰면 두 번째 '가 닫는 따옴표인지 문자 '를 나타내는지 컴파일러가 구분할 수 없다. 그래서 키보드로 직접 입력할 수 없는 문자들은 좀 특수한 방법으로 표현하는데 이를 확장열(Escape Sequence)이라고 한다. 확장열은 백슬레쉬(\) 문자 다음에 기호 하나를 써서 표현한다.

    확장열

    코드

    설명

    \a

    0x07

    소리

    \b

    0x08

    스페이스

    \t

    0x09

    \n

    0x0a

    개행

    \x##

    0x##

    16 코드

    \###

    0###

    8 코드

    \\

    0x5c

    백슬레쉬

    \'

    0x27

    홑따옴표

    \"

    0x22

    겹따옴표

    \?

    0x3f

    물음표

    개행 코드는 \n으로 표현하는데 이 코드는 First예제에서 이미 실습해 보았다. 탭이나 백 스페이스 같은 문자도 직접 키보드로 입력해서는 따옴표안에 표기할 수 없기 때문에 확장열로 표기해야 한다. 홑따옴표 문자 하나는 '''와 같이 표기할 수 없고 확장열을 사용해서 '\''와 같이 표기해야 한다.

    확장열 표기에 \문자를 사용하기 때문에 \문자 자체도 확장열이 될 수밖에 없다. 16진 코드는 키보드에 없는 문자에 대해 코드를 직접 쓸 때 사용한다. 'A'는 '\x41'과 동일하다. 16진 코드를 확장열로 표기할 때 \다음의 x는 반드시 소문자로 써야 하며 대문자는 인정하지 않는다. 문자열 상수내에 16진 코드를 직접 쓸 경우 16진수로 인식되는 모든 문자를 확장열로 취급한다는 점을 주의하자. '\x53trike"는 \x53이 s이므로 "strike"이지만 "\-3econd"는 \다음의 53ec까지를 16진수로 해석해 버리므로 "second"가 되지 않고 에러로 처리된다. 왜냐하면 53ec는 문자 코드 범위 바깥이므로 하나의 문자가 아니기 때문이다. 이런 경우는 16진 코드를 쓰지 않거나 다른 방법으로 문자열을 표기해야 한다.

    확장열을 쓰는 이유는 꼭 키보드로 표현하지 못해서뿐만 아니라 환경에 따라 달라질 수 있는 코드를 논리적으로 표현하기 위해서이다. 개행을 하는 방식은 시스템마다 다른데 윈도우즈에서는 CR, LF의 조합으로, 유닉스는 LF만으로, 매킨토시는 CR만으로 개행 문자를 표현한다. C는 이런 방식에 상관없이 개행을 표현할 수 있는 확장열을 제공하고 프로그래머는 개행이 필요할 때 \n이라고만 적으면 된다.

    탭의 경우 일정한 자리를 띄우는 것이 아니라 현재 위치에서 다음 탭 위치로 이동하는 기능을 하므로 소스상에 입력된 탭에 의해 띄워진 빈칸과 실제 출력될 위치의 탭 크기가 다를 수 있다. 그래서 탭키를 문자열 상수에 직접 쓰지 않고 \t 확장열로 표기하여 출력되는 상황에 맞게 탭을 적용하도록 한다.


    반응형
    반응형

    출처 : http://sungho0459.blog.me/40142019746






    ---------------------------------------------------------------------------------------------------------

     

    extern "C"를 자주 쓰긴 하지만 정확한 내용을 몰라 'jimbo73.egloos.com/1486292'으로 부터 퍼온 상태로 수정한 내용입니다.

    컴파일러는 링커가 링킹작업시 오브젝트간 함수 이용 및 위치를 파악할 수 있도록, 컴파일시 사용된 함수에 관련한 정보(linkage)를 오브젝트 파일에 기록합니다.

     

     

    linkage란 컴파일 시 함수이름 앞 또는 뒤에 '_'(언더바)등의 심볼을 덧붙이는 것을 말하는 것으로, C와 C++은 컴파일시 오브젝트 파일에 함수명, 변수명등에 심볼을 기록하는 방식이 다르다.

     

    C에서는 함수의 이름이 유일하기 때문에 (Overloading 지원하지 않음) 함수 앞에 '_'등의 심볼만을 붙이면 된다. 하지만 C++에선 overloading을 지원하기 때문에 같은 이름의 함수를 여러개 가질 수 있다. 그래서 함수를 구분하기 위해 함수이름만으로 구분할 수 없게 되고, 인자의 개수와 데이터형에 대한 정보까지 가지게 되어 linkage 정보가 서로 다르게 된다.

     

    그러므로 C와 C++을 혼합하여 사용 시, 두 언어간의 linkage방식이 틀려 함수 이용에 문제가 발생할 수 있다.

     

    C와 C++을 혼합하는 프로그램에서는 link시 함수명을 각각의 방식으로 찾을 수 있도록 C언어 부분인지, C++ 부분인지를 확실히 명시해 주어야하며, 이때 linkage에 대한 지시자 역활을 하는 extern "C"를 사용한다.

     

    사용방법 예제

    #ifdef __cplusplus

      extern "C"{

    #endif

     

        int func1();

        int func2();

     

    #ifdef__cplusplus

      }

    #endif

    < #ifdef __cplusplus 이 문구는 C++이라면 이라는 컴파일러 지시자(조건문)입니다. >

     

     

     

     

     

    p.s 간단히 정리해주신 jimbo73님에게 감사의 글을 올립니다.

    [출처] extern "C"|작성자 에몬

    반응형
    반응형

    http://blog.naver.com/garins/90081899794




    class 클래스명 (전방선언)


    //------------------------
    // Temp.h 파일
    class CTemp
    {
        //...
    }
    //------------------------
    // CApp.h 파일

    class CTemp;//전방 선언

    class CApp
    {
    public :
    //    CTemp m_Temp;    //전방선언 사용 불가능
    //    CTemp* m_Temp;   //전방선언 사용 가능
    }
    //------------------------

     


    전방선언 사용시 구현파일 (*.cpp)에 #include "*.h" (링크) 해주어야 한다.

    최대한 링크 사용보다는 가능하면 전방선언을 사용할수 있다면 
    실천하는 것이 빌드 시간을 단축하는 지름길이다.

     

    인크루드로 인해서 소스가 엉키는 일을 획기적으로 줄일수 있는듯 하다..

    전방선언으로 인해서 상속관계가 아니라면 굳이 .h에 인크루드를 사용할 필요가 없을듯

    원본 : http://idrose1025.egloos.com/2201851

    반응형
    반응형

    출처 : http://serious-code.net/moin.cgi/CppDebuggingTips#head-542083e07fc1db7784e0c88b48594020973726bd


    스택 오버플로우 발생시 덤프 기록하기

      스택 오버플로우 발생시의 덤프 기록은 약간 다르게 처리를 해줘야한다. 어려운 건 아니고, 그냥 다른 스레드를 하나 생성해서 그쪽으로 스레드 핸들과 예외 포인터를 넘겨서 덤프를 기록하면 된다. 현재 스레드에서는 덤프 함수를 호출할 스택마저 모자랄 수 있기 때문이다. 하지만 다른 스레드를 하나 따로 생성하면 새로 생성한 스레드에서는 스택 공간이 충분하기 때문에 덤프를 무사히 기록할 수 있는 것이다. (스택은 스레드별로 유지되니까...)
      typedef struct _DUMP_PARAMETER
      {
          HANDLE              Thread;
          PEXCEPTION_POINTERS ExPtrs;
      } DUMP_PARAMETER, *PDUMP_PARAMETER;
      
      DWORD WINAPI WriteDump(LPVOID param)
      {
          PDUMP_PARAMETER dumpParam = reinterpret_cast<PDUMP_PARAMETER>(param);
          ...여기서 실제 덤프를 수행...
      }
      
      LONG WINAPI HandleException(PEXCEPTION_POINTERS exPtrs)
      {
          if (exPtrs)
          {
              PDUMP_PARAMETER dumpParam = (PDUMP_PARAMETER)malloc(sizeof(DUMP_PARAMETER));
              dumpParam->Thread = GetCurrentThread();
              dumpParam->ExPtrs = exPtrs;
              if (exPtrs->ExceptionRecord->ExceptionCode != EXCEPTION_STACK_OVERFLOW)
              {
                  WriteDump(dumpParam);
              }
              else // 스택 오버플로 발생 시에는 별도의 스레드를 생성해서 덤프를 기록한다.
              {
                  HANDLE hThread = CreateThread(NULL, 102400, WriteDump, dumpParam, 0, NULL);
                  WaitForSingleObject(hThread, INFINITE);
                  CloseHandle(hThread);
              }
          }
          return EXCEPTION_EXECUTE_HANDLER;
      }
      
      void main()
      {
          ...
          // 예외 처리 핸들러를 설정한다.
          SetUnhandledExceptionFilter(HandleException);
          ...
      }
       

      1 메모리 디버그 코드

        VisualCpp 메모리 코드

        의미
        0xCD, 0xCDCDCDCD 최초로 할당된 메모리
        0xFD, 0xFDFDFDFD 유저가 할당한 메모리 뒤에 붙는 바운드 체크용 메모리
        0xDD, 0xDDDDDDDD 삭제된 메모리. _CRTDBG_DELAY_FREE_MEM_DF 플래그 값이다.
        0xCC, 0xCCCCCCCC 초기화되지 않은 지역 변수. /GX 옵션을 켜야 한다.
        0xABABABAB LocalAlloc 함수로 할당한 메모리 뒤에 붙는 바운드 체크용 메모리
        0xBAADF00D LocalAlloc 함수로 할당한 메모리
        0xFEEEFEEE HeapAlloc 함수를 위해 확보는 해놨으나, 아직 실제로 할당되지는 않은 메모리. 또는 HeapFree 함수를 통해 해제된 메모리
       

    반응형
    반응형

    저자 : 최흥배 마이크로소프트 Visual C++ MVP

    0. 서두 5
    0.1 C++의 문제점 5
    0.2 C++는 시대에 뒤 떨어진 프로그래밍 언어일까요? 6
    1. C++0x에 대해서 7
    1.1 C++의 새로운 표준 C++0x 7
    1.2 Visual C++의 이전 버전과의 호환성 7
    1.3 Visual C++ 9와 Visual C++ 10 8
    2. auto 9
    2.1 정적 언어와 동적 언어의 차이 9
    2.2 C#의 var 9
    2.3 C++에서 STL(표준 템플릿 라이브러리)를 사용할 때 불편한 점 10
    2.4 컴파일 타임 때 타입을 정하는 'auto' 10
    2.5 auto를 사용한 예제 11
    2.5.1 지역 변수로 사용 11
    2.5.2 클래스 정의에 사용 12
    2.5.3 STL에서 사용 12
    2.6 핵심 요약 14
    3. static_assert 15
    3.1 assert와 #error 15
    3.2 assert와 #error를 사용할 수 없을 때 15
    3.3 static_assert의 형식 15
    3.4 static_assert 사용 예 16
    3.4.1 프리프로세스 지시어 대체 16
    3.4.2 템플릿에서 사용 16
    3.4.3 변수의 크기 조사 17
    3.5 핵심 요약 17
    4. 우측 값 참조 (RValue Reference) 18
    4.1 C++98/03에서의 실수 18
    4.2 좌측 값(LValue)과 우측 값(RValue) 19
    Contents
    Contents
    4.3 좌측 값 참조와 우측 값 참조 20
    4.4 Move semantics 21
    4.5 Move 생성자와 Move 대입 연산자 22
    4.6 Move semantics에 의한 성능 향상 23
    4.7 move 생성자와 move 대입 연산자 사용 예 24
    4.8 std::move 25
    4.9 VC 10에서의 STL과 우측 값 참조 26
    4.10 우측 값 참조를 사용할 때 주의할 점 28
    4.11 우측 값 참조와 좌측 값 참조의 함수 오버로드 29
    4.12 우측 값 참조는 우측 값이 아니다 30
    4.13 Perfect Forwarding 30
    4.14 핵심 요약 34
    5. 람다 (lambda) 35
    5.1 C#과 람다 35
    5.2 C++에서 STL 알고리즘을 사용할 때 불편했던 점 36
    5.3 C++에서의 람다 사용 법 37
    5.3.1 문법 37
    5.3.2 람다를 변수에 대입 37
    5.3.3 람다를 함수의 인자로 사용하기 38
    5.3.4 람다의 파라미터 38
    5.3.5 반환 값 넘기기 39
    5.4 캡쳐 39
    5.5 클래스에서 람다 사용 43
    5.6 STL의 find_if에서 람다 사용 44
    5.7 람다 식을 STL 컨테이너에 저장 45
    5.8 람다를 반환하는 함수 46
    5.9 람다에서의 재귀 46
    5.10 핵심 요약 47
    6. decltype 48
    6.1 템플릿 프로그래밍 시 문제 48
    6.2 decltype 사용하기 48
    7. nullptr 49
    7.1 nullptr이 필요한 이유 49
    7.2 nullptr 구현 안 50
    7.3 nullptr 사용 방법 50
    7.4 nullptr의 올바른 사용과 틀린 사용 예 50
    8. 참고 52
    0. 서두
    Visual C++ 10과 C++0x 0. 서두
    5
    0.1 C++의 문제점
    C++가 처음 나왔을 때는 다양한 하드웨어로의 이식성이 높고, 하드웨어를 잘 활용하여 고성능 프로그
    램을 만들 수 있으며 객체지향 프로그래밍을 할 수 있는 구조와 표준 템플릿 라이브러리(STL) 덕택에
    C 언어 보다 더 좋은 생산성을 얻을 수 있어서 단숨에 C 언어를 제치고 프로그래머에게 가장 인기 있
    는 프로그래밍 언어가 되었습니다. 그러나 지금은 다른 언어들에게 최고의 자리를 내어 주었습니다.
    C++ 언어가 나왔을 때는 하드웨어 성능이 지금보다 빈약한 시기로 프로그램이 작은 메모리와 낮은
    CPU 점유율을 가지도록 성능에 대해 지금보다 더 많이 신경을 쓰면서 프로그램을 만들었지만, 지금은
    멀티 코어 CPU와 기가 바이트 단위의 메모리를 가진 컴퓨터가 일반화된 시대로 바뀌어 생산성을 더
    중요시 하고 있습니다.
    C++가 예전만큼의 인기를 얻지 못하는 것은 근래에 나온 언어에 비해서 사용하기가 쉽지 않고 생산성
    이 떨어지기 때문이라고 생각합니다.
    “왜 C++이 어려울까요?”라고 트위터에 질문을 올려보았습니다. 이 질문에
    @whatthepaul님은
    “사용자가 알아야 할 것이 너무 많아서가 아닐까요? 그만큼 속도에서는 이득이 있는 거겠지만
    생산성은 떨어지겠죠. 점점 생산성이 중요해지는 추세인데 좀 반대에 있는 느낌이죠 -_-;”,
    @birdkr님은
    “문법이나 포인터 개념도 어렵지만, 저는 지원해주는 기본 라이브러리가 턱없이 부족한 것도 한
    원인이라 생각합니다. 요즘 인기 있는 언어들처럼 뭔가 뚝딱뚝딱하면 그럴 듯 한 것이 나와야 하
    는데 C++은 간단한 것도 쉽게 만들 수 없으니까요.”
    라는 의견을 주셨습니다. 두 분의 의견은 제 주위의 C++ 개발자들이나 인터넷의 개발자 커뮤니티나 블
    로그 등에 있는 C++에 대한 불만과 비슷합니다.
    처음 C++이 만들어질 때에 비해서 지금은 세상도 많이 변했고, 컴퓨터 하드웨어 성능도 훨씬 더 좋아
    졌습니다. 이젠 C++도 변화해야 할 때입니다. 그리고 다른 프로그래밍 언어들처럼 생산성을 향상 시켜
    야 합니다.
    Visual C++ 10과 C++0x 0. 서두
    6
    0.2 C++는 시대에 뒤 떨어진 프로그래밍 언어일까요?
    요즘은 컴퓨터 하드웨어 성능이 예전에 비해서 훨씬 좋아져서 프로그램을 만들 때 성능보다는 높은 품질로
    빨리 만드는 것이 중요하다고 했습니다. 그러나 정말 성능을 신경 쓰지 않아도 될까요?
    어떤 프로그램이냐에 따라서 답변이 틀릴 것이라고 생각합니다. 예전에 비하면 성능에 크게 신경 쓰지 않아
    도 괜찮은 분야가 더 늘었지만 아직도 몇몇 분야에서는 성능에 신경을 써야 합니다. 제가 알고 있는 분야 중
    임베디드와 게임은 아직도 성능에 신경을 써야 하는 분야합니다.
    특히 제가 일하고 있는 게임 업계는 언제나 하드웨어의 발전 속도보다 게이머들이 이전보다 더 좋은 고화질
    의 그래픽, 더 사람 같은 AI를 원하고 있기 때문에 프로그래머들은 언제나 성능에 많은 신경을 써야 합니다.
    또 HD급의 그래픽을 사용하는 게임이 아니라면 낮은 사양의 CPU와 작은 메모리를 가진 하드웨어에서도 게
    임을 실행되게 하기 위해 최적화에 많은 시간을 들입니다.
    아직은 C#이나 Java로는 C++과 같은 높은 성능을 필요로 하는 게임을 만들기가 어렵기 때문에 주류급 게
    임을 만들 때는 사용할 수 없습니다.
    아무리 하드웨어 사양이 높아지고 있더라도 하드웨어를 최대한 활용하여 높은 성능을 필요로 하는 프로그램
    을 만들어야 하는 분야는 여전히 있습니다. 이런 곳에서는 C#이나 Java로는 턱 없이 부족합니다. C++가 적
    격입니다.
    C++의 필요성은 충분히 알고 있지만 C++의 학습의 어려움, 낮은 생산성 등 부족한 부분을 빨리 누군가 메
    꾸워 주기를 바라는 분들이 많이 있으리라 생각합니다. 바로 이런 부족한 부분을 메꾸기 위해서 C++의 새
    로운 표준이 현재 만들어지고 있습니다. 그리고 기쁜 소식은 표준 작업이 끝나기 이전에 새로운 C++의 기
    능을 새로 나올 Visual C++을 통해서 사용할 수 있습니다
    Visual C++ 10과 C++0x 1. C++0x에 대해서
    7
    1.1 C++의 새로운 표준 C++0x
    C++은 처음 표준을 만들 때 다양 사람들의 의견을 반영하여 완성도 있게 만들어졌고, 이미 C++로 만
    들어진 프로그램이 많이 있어서 C#이나 Java처럼 새로운 표준이 자주 만들어지지 않아서 어떤 분들은
    C++은 이제 과거의 언어로 더 이상 발전하지 않는 언어라고 생각하시는 분도 있으리라 생각합니다.
    C++은 결코 과거에 머물러 있는 언어가 아닙니다. 그 증거로 몇 년전부터 새로운 C++의 새로운 표준
    이 만들어지고 있습니다.
    새로운 표준이 될 개정안을 임시적으로 C++0x라고 부르고 있습니다(C++0x 이전에는 C++98과
    C++03 라고 부르는 표준안이 있었습니다).
    표준 위원회는 2009년 안에 표준을 확정하기 위해서 2006년까지 받았던 제안을 중심으로 표준 작업
    을 하고 있다고 합니다. 하지만 C++0x라고 가칭을 붙이면서까지 2009년 안에 표준 작업을 끝내려고
    했지만 결과적으로 2010년을 넘겼습니다. 2009년을 넘겼으므로 C++0x라는 가칭의 이름도 바뀌어야
    하지만 가칭을 바꾸면 혼란이 생길 수 있으므로 C++0x라는 가칭을 그대로 사용하기로 했습니다.
    C++0x는 코어 언어의 기능 추가와 표준 C++ 라이브러리(STL)를 확장이라는 크게 두 개의 분류로 나
    누어서 작업을 하고 있습니다.
    1.2 Visual C++의 이전 버전과의 호환성
    C++의 창시자이자 C++ 표준 위원회의 멤버인 Bjarne Stroustrup는 새로운 C++ 표준은 이전의 표준
    과 100% 호환성을 가진다고 합니다. 즉 이전에 만들었던 C++ 코드들이 표준을 준수했다면 새로운 표
    준에서 컴파일 하는데 아무런 문제가 없습니다.
    예전에 Visual C++ 6(이하 VC6)에서 Visual C++ 7(이하 VC 7)로 넘어갈 때 VC 6에서 만들었던
    코드가 VC 7에서는 에러와 경고를 내서 순조롭게 넘어가지 못한 경우가 있었습니다. 지금도 이것 때문에
    아직도 VC 6을 사용하고 있는 곳도 있는 것으로 알고 있습니다.
    VC 6에서 만들었던 코드가 VC 7에서 문제가 된 이유는 VC 6는 C++98 표준이 확립되기 전에 나와서
    VC 6에서 만들었던 코드 중에는 표준에 맞지 않는 코드가 있기 때문입니다. 그러나 VC 7은 표준을
    거의 완벽하게 준수하고 있어서 VC 6에서 만들었던 코드 중 표준에 맞지 않는 코드는 에러나 경고를
    발생시킵니다.
    VC 7부터는 표준을 거의 100% 준수하고 있으므로(참고로 100% C++ 표준을 준수한 상업용 컴파일
    러는 거의 없습니다) VC 7에서부터 만들었던 코드는 Visual C++ 10(이하 VC 10)에도 문제 없이 컴
    파일 할 수 있습니다.
    즉 VC 7, VC 8, VC 9에서 컴파일에 문제가 없는 코드라면 VC 10에서도 수정 없이 그대로 사용할
    수 있습니다.
    1. C++0x에 대해서
    Visual C++ 10과 C++0x 1. C++0x에 대해서
    8
    1.3 Visual C++ 9와 Visual C++ 10
    C++ 표준위원회에서 C++0x에서 정책적으로 라이브러리 부분을 언어적인 부분보다 우선 시 하자고
    정했기 때문에 tr1이라는 라이브러리가 VC 9가 나올 무렵에 나와서 VC 9가 출시 된 이후 Visual
    Studio Service Pack을 통해서 VC 9에서 사용할 수 있게 되었습니다.
    VC 10에서는 VC 9에 추가된 tr1과 달리 언어적인 부분에서의 새로운 기능이 구현 되었습니다.
    언어적인 부분의 개선에 의해서 C++ 언어 표현력이 증대되었고 성능적 측면에서도 개선이 이루어졌습
    니다.
    VC 10에 구현된 C++0x의 새로운 기능은 VC 9의 tr1과 달리 언어적 측면에서의 새로운 기능이라서
    새롭게 바뀐 C++을 이전에 비해 훨씬 더 강하게 체감할 수 있습니다.
    VC 10에 구현된 C++0x의 새로운 기능은 auto, static_assert, RValue Reference, lambda,
    decltype, nullptr입니다.
    다음 장부터는 새로운 기능들에 대해서 설명할 테니 이전 보다 더 편리하고 강력해진 C++의 새로운
    기능을 직접 느껴보시기 바랍니다. 보통의 C++ 프로그래머가 아닌 새로운 C++ 표준을 사용하는
    C++0x 프로그래머가 되시기 바랍니다.
    Visual C++ 10과 C++0x 2. auto
    9
    2.1 정적 언어와 동적 언어의 차이
    근래에 Ruby나 Python과 같은 스크립트 언어가 많은 인기를 얻고 있습니다. Ruby나 Python 같은
    언어를‘동적 언어’라고 합니다. 반대로 제가 주로 사용하는 언어인 C++는‘정적 언어’라고 합니다.
    정적 언어와 동적 언어는 여러가지 차이점이 있습니다. 그중 정적 언어는 변수 type을 선언이나 정의
    할 때 명시적으로 지정해야 합니다. 그러나 동적 언어인 Ruby, Python은 변수 type을 명시적으로 지
    정할 필요가 없어서 편리합니다.
    < 코드 2-1. C/C++에서의 지역 변수 정의 >
    <코드>
    void BuyItem()
    {
    int Money = 500;
    ........
    }
    </코드>
    < 코드 2-2. Ruby에서의 지역 변수 정의 >
    <코드>
    def BuyItem
    Money = 500;
    ......
    end
    </코드>
    2.2 C#의 var
    C#은 C++과 같은 정적 언어이지만 var라는 키워드를 사용하여 변수 type을 명시적으로 지정하지 않
    아도 됩니다. 동적 언어와 차이점은 변수의 type을 실행할 때가 아닌 컴파일 할 때 결정합니다.
    C#의 var 키워드를 사용하면 코딩 시 번거롭거나 코드의 가독성을 나쁘게 하는 코드를 없애 수 있어서
    아주 유용합니다. var는 특히 C#에서 LINQ를 사용할 때 자주 사용합니다.
    < 코드 2-3. LINQ에서 var를 사용할 때와 사용하지 않을 때 비교>
    <코드>
    // LINQ에서 var를 사용하지 않는 경우
    IEnumerable<IGrouping<string, Student>> studentQuery3 =
    from student in students group student by student.Last;
    2. auto
    Visual C++ 10과 C++0x 2. auto
    10
    // LINQ에서 var를 사용한 경우
    var studentQuery3 = from student in students group student by student.Last;
    </코드>
    <코드 2-3>에서 var 키워드를 사용하여 LINQ를 사용하면 코딩 해야 할 량도 줄어들고 코드 가독성도
    var를 사용하지 않을 때에 비해 훨씬 더 좋아진 것을 알 수 있습니다.
    2.3 C++에서 STL(표준 템플릿 라이브러리)를 사용할 때 불편한 점
    C++에서 STL을 사용할 때 코딩이 불편할 때가 있습니다. 예를 들면 list와 같은 STL 컨테이너에서 컨
    테이너에 있는 모든 요소를 순회할 때입니다.
    < 코드 2-4. C++에서 list 사용 >
    <코드>
    list<int> NumList;
    NumList.push_back( 10 );
    for( list<int>::iterator iter = NumList.begin();
    iter != NumList.end();
    ++iter )
    {
    ……
    }
    </코드>
    list 컨테이너의 첫 번째 요소를 얻기 위해서 반복자를 정의할 때 꽤 길게 코딩 해야 됩니다(map과 같
    은 연관 컨테이너는 훨씬 더 깁니다). 위의 경우는 그나마 좀 짧은 편이고 템플릿을 사용한 클래스를
    list의 type으로 사용하는 경우는 아주 길어집니다.
    <코드 2-4>를 보면 앞선 <코드 2-3>에서 var 키워드를 사용하지 않을 때와 비슷한 불편함이 있다는
    것을 알 수 있습니다. 만약 C++에도 C#과 같이 var 라는 것이 있으면 이 문제를 쉽게 해결할 수 있겠죠?
    C++0x에서는 바로 var와 같은 것을 만들어서 <코드 2-4>과 같은 불편함을 해결 하였습니다.
    해결 방법은 C++0x에서 새로 생긴‘auto’라는 키워드를 사용하는 것입니다.
    2.4 컴파일 타임 때 타입을 정하는 'auto'
    auto를 사용하면 변수를 정의할 때 명시적으로 type을 지정하지 않아도 됩니다. auto로 정의한 변수를
    초기화할 때 type을 결정합니다. 즉 C#의 var와 같이 컴파일 타임 때 변수의 type을 결정합니다. 하지
    만 클래스의 멤버 변수나 전역변수, 함수의 인자로는 auto를 사용할 수는 없습니다.
    auto가 복잡한 개념은 아니지만 변수를 정의할 때는 언제나 type을 지정해야 한다고 아주 당연하게 생
    각하는 C++ 프로그래머로서는 조금 과장을 해서 auto는 아주 획기적이라고 느끼시는 분들도 있으리라
    생각합니다(저는 C++0x를 공부하기 전에는 C++에서는 절대 auto 같은 기능은 지원하지 않고 이런
    것은 C#과 같은 근래의 언어나 스크립트 언어에서만 가능한 것이라고 생각하고 있었습니다).
    Visual C++ 10과 C++0x 2. auto
    11
    auto 키워드는 C#의 var와 비슷하고, 개념이나 사용 방법이 아주 간단합니다. ‘변수를 정의할 때 명시
    적으로 type을 지정하지 않고 컴파일 타임 때 결정’하게 해주는 키워드라고 기억하시면 됩니다.
    그럼 auto를 어떻게 사용하는지 예제 코드를 보겠습니다.
    2.5 auto를 사용한 예제
    2.5.1 지역 변수로 사용
    문자열과 정수를 담을 변수를 auto를 사용하여 정의하면 아래와 같습니다.
    < 코드 2-5. 문자열과 정수 변수에 auto 사용 >
    <코드>
    #include <iostream>
    using namespace std;
    int main()
    {
    // char*
    auto NPCName = "BugKing";
    cout << NPCName << endl;
    // int
    auto Number = 1;
    cout << Number << endl;
    getchar();
    return 0;
    }
    </코드>
    < 결과 2-5 >
    또 당연히 포인터나 참조, const도 사용할 수 있습니다.
    < 코드 2-6. 포인터, 참조, const에 사용 >
    <코드>
    #include <iostream>
    using namespace std;
    int main()
    {
    int UserMode = 4;
    auto* pUserMode = &UserMode;
    cout << "pUserMode : Value - " << *pUserMode << ", address : " << pUserMode <<
    endl;
    Visual C++ 10과 C++0x 2. auto
    12
    auto& refUserMode = UserMode;
    refUserMode = 5;
    cout << "UserMode : Value - " << UserMode << " | refUserMode : Value - " <<
    refUserMode << endl;
    getchar();
    return 0;
    }
    </코드>
    < 결과 2-6 >
    2.5.2 클래스 정의에 사용
    클래스를 정의할 때도 사용할 수 있습니다.
    < 코드 2-7. 클래스 생성 때 auto 사용 >
    <코드>
    struct CharacterInvenInfo
    {
    int SlotNum;
    int ItemCode;
    };
    ........
    auto* CharInven = new CharacterInvenInfo();
    ........
    </코드>
    2.5.3 STL에서 사용
    auto 키워드는 템플릿 프로그래밍이나 STL을 사용할 때 진가를 더 발휘합니다.
    auto가 없을 때는 STL의 컨테이너를 사용할 때 반복자를 정의 하기 위해 길게 코딩을 하던가 또는
    typedef를 사용하였지만 auto가 생겨서 이런 불편함이 없어졌습니다.
    <코드>
    typedef std::list<MCommand*> LIST_COMMAND;
    LIST_COMMAND::iterator iter = m_listCommand.begin();
    </코드>
    를 아래처럼 바꿀 수 있습니다.
    <코드>
    Visual C++ 10과 C++0x 2. auto
    13
    프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
    가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
    추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
    표 1 ALM의 3대 구성 요소
    auto iter = m_listCommand.begin();
    </코드>
    아래의 예제를 보면 auto를 사용하여 STL 사용이 얼마나 간단해졌는지 쉽게 알 수 있습니다.
    < 코드 2-8. vector의 요소를 순회할 때 auto 사용 >
    <코드>
    #include <iostream>
    #include <vector>
    using namespace std;
    struct Item
    {
    int ItemCode;
    int Money;
    int SkillCode;
    };
    int main()
    {
    cout << "Use vector Iterator - 1" << endl;
    vector< int > ItemCodeList;
    ItemCodeList.push_back( 20 );
    ItemCodeList.push_back( 30 );
    ItemCodeList.push_back( 40 );
    for( auto IterPos = ItemCodeList.begin(); IterPos != ItemCodeList.end(); ++IterPos)
    {
    cout << "ItemCode : " << *IterPos << endl;
    }
    cout << endl;
    cout << "Use vector Iterator - 2" << endl;
    vector< Item > ItemList;
    Item item1; item1.ItemCode = 1; item1.Money = 100; item1.SkillCode = 0;
    Item item2; item2.ItemCode = 2; item2.Money = 200; item2.SkillCode = 10;
    Item item3; item3.ItemCode = 3; item3.Money = 300; item3.SkillCode = 0;
    ItemList.push_back( item1 );
    ItemList.push_back( item2 );
    ItemList.push_back( item3 );
    for( auto IterPos = ItemList.begin(); IterPos != ItemList.end(); ++IterPos)
    {
    cout << "ItemCode : " << IterPos->ItemCode << ", Money : " << IterPos-
    >Money << endl;
    }
    cout << endl;
    getchar();
    return 0;
    }
    </코드>
    Visual C++ 10과 C++0x 2. auto
    14
    < 결과 2-8 >
    auto는 정말 단순한 개념과 사용 방법이면서 바로 C++ 프로그래머의 작업에 많은 도움을 주는 기능입
    니다. 아마 모든 C++ 프로그래머들이 많이 애용하는 기능 중의 하나가 될 것 이라고 생각합니다.
    2.6 핵심 요약
    auto를 사용하면
    1. 지역 변수를 정의 때 명시적으로 type을 지정하지 않아도 됨
    2. 컴파일 타임 때 type을 결정
    3. 코딩이 간편해지고, 코드 가독성이 좋아짐
    Visual C++ 10과 C++0x 3. static_assert
    15
    프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
    가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
    추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
    표 1 ALM의 3대 구성 요소
    3.1 assert와 #error
    C++로 프로그래밍할 때 버그나 에러가 발생할 위험이 있는 곳에 경고를 발생시켜 문제를 빨리 발견하
    기 위해서 assert 매크로나 #error 프리프로세서 지시어를 사용합니다. (프리프로세스는 #ifdef 등을
    사용할 때를 말합니다).
    assert와 #error 중 보통 assert를 자주 사용하며 assert는 논리적인 오류 찾기, 작업 결과 확인, 처리
    해야 할 오류 조건 테스트를 할 때 사용합니다.
    assert는 프로그램이 실행 될 때 평가 됩니다(디버그 모드에서 사용합니다).
    < 코드 3-1. assert 사용 예 >
    </코드>
    PLAYER* pPlayer;
    ……
    assert( NULL != pPlayer )
    </코드>
    3.2 assert와 #error를 사용할 수 없을 때
    assert는 실행 시에 사용하고, #error는 프리프로세스에 사용하기 때문에 템플릿 실체화 시(컴파일 타
    임)에는 이것들을 사용할 수 없습니다. 버그의 발견은 프로그램이 실행될 때 발견하는 것보다 컴파일
    할 때 발견하면 버그를 잡는데 소요되는 시간이 짧아집니다.
    C++0x에서 새로 생긴 static_assert는 컴파일 할 때 평가됩니다.
    static_assert는 특히 컴파일 타임에서 실체화할 템플릿의 전제 조건을 조사할 때 사용하면 좋습니다.
    예를 들면 Stack이라는 클래스 템플릿을 정의할 때 템플릿 파라미터로 type과 크기를 사용할 때 크기
    가 일정 크기 이상일 때만 컴파일 되기를 바란다면 static_assert를 사용하면 딱 좋습니다.
    3.3 static_assert의 형식
    static_assert의 형식은 다음과 같습니다.
    원형
    static_assert “( constant-expression”,“ error-message'') ;
    파라미터
    “constant-expression”- 검사할 조건 식
    “error-message”- 조건이 false일 경우 출력할 error 메시지
    결과
    constant-expression가 false일 경우 컴파일러는 에러 메시지를 출력합니다.
    static_assert는 다음과 같은 경우에 사용하면 유용합니다.
    3. static_assert
    Visual C++ 10과 C++0x 3. static_assert
    16
    1. 기본 타입(int, long 등)이나 유저 정의 타입(class, struct 등으로 만든 타입)의 크기를 확인하고
    싶을 때
    2. 어떤 타입의 최대 크기를 넘어서는지 확인하고 싶을 때
    3.4 static_assert 사용 예
    3.4.1 프리프로세스 지시어 대체
    < 코드 3-2. 상수 값의 크기 조사 >
    <코드>
    #include <iostream>
    using namespace std;
    const int MAX_LEVEL = 120;
    int main()
    {
    static_assert( MAX_LEVEL <= 100, "Warring - MAX_LEVEL" );
    return 0;
    }
    </코드>
    < 그림 3-1. <코드 3-2>의 컴파일 결과 >
    <코드 3-2>는 MAX_LEVEL의 값이 100을 넘으면 컴파일 할 때 에러를 출력합니다.
    VC++ 10의 경우 편리한 IntelliSense가 컴파일 하기 전에 붉은 밑줄로 에러가 있음을 사전에 알려주
    기도 합니다.
    < 그림 3-2. 인텔리센스의 에러 통지 >
    3.4.2 템플릿에서 사용
    < 코드 3-3. Stack 클래스 템플릿의 최소 스택 크기 조사 >
    <코드>
    #include <iostream>
    using namespace std;
    Visual C++ 10과 C++0x 3. static_assert
    17
    프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
    가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
    추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
    표 1 ALM의 3대 구성 요소
    template< typename T1, int StackSize >
    class MYSTACK
    {
    static_assert( StackSize >= 10, "Stack Size Error" );
    public :
    MYSTACK() : data( new T[StackSize] )
    {
    }
    private:
    T1* data;
    };
    int main()
    {
    MYSTACK< int, 5 > MyStack;
    return 0;
    }
    </코드>
    < 그림 3-3. <코드 3-3>의 컴파일 결과 >
    3.4.3 변수의 크기 조사
    < 코드 3-4. int 타입의 크기 조사 >
    <코드>
    #include <iostream>
    using namespace std;
    int main()
    {
    static_assert( sizeof(int) == 4, "not int size 4" );
    return 0;
    }
    </코드>
    static_assert의 개념이나 사용 법이 간단하기 때문에 위의 예제 코드를 보면 어떻게 사용하고, 어디에
    사용하면 좋을지 알 수 있으리라 생각합니다.
    3.5 핵심 요약
    1. assert와 비슷한 조건 조사를 할 수 있음
    2. 컴파일 타임 때 사용하여 프로그램 실행 전에 문제를 찾을 수 있음
    3. 템플릿 프로그래밍에 사용하면 특히 유용
    4. 우측 값 참조 (RValue Reference)
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    18
    4.1 C++98/03에서의 실수
    C++98/03에서는 너무 과하다 싶을 정도로 추상화와 효율성을 같이 가져가려고 하다가 실수를 범했습
    니다. 이 실수는 추상화와 효율성의 조합을 위해 불필요한 복사를 초래 하였습니다.
    int와 같은 작은 것의 복사는 괜찮지만 중량급 오브젝트에서는 무시하기 힘듭니다. 다행히 RVO나
    NRVO의 도움으로 불필요한 복사 생성자를 제외하여 어느 정도 문제를 경감 시켜주지만 이것 만으로
    는 모든 상황에서 과도한 복사 문제를 해결해 주지 못합니다.
    의미 없는 복사
    위에서 언급한 불필요한 복사의 예를 들면
    (a) vector에서 새로운 요소를 추가할 때 확장하는 경우,
    (a) string 객체간의 결합 등이 있습니다.
    < 그림 4-1. vector의 확장 >
    <그림 4-1>과 같이 vector를 확장할 때는 새로운 메모리 공간을 할당한 후 기존 요소를 하나씩 복사한
    후 앞서 할당한 영역을 제거합니다. 이것은 영리한 방법은 아닙니다.
    vector를 확장할 때 기존 요소를 그냥 그대로 사용할 수 있다면 기존 요소를 복사하지 않아도 되고, 기
    존에 사용하던 메모리 영역을 없애 필요도 없습니다. 바꾸어 말하면“복사기로 문서 하나를 복사를 하
    는데 복사가 끝난 후 원본은 버린다”라는 경우와 비슷합니다. 원본을 버린다면 궂이 복사를 할 필요도
    없습니다. 너무나 멍청한 짓입니다. 그런데 C++ 03까지는 위에서 예를 든 (a)와 (b)를 할 때 복사기로
    복사를 하고 원본을 버리는 것과 같은 행동을 했습니다.
    (참고로 (a)와 (b)에서는 의미 없는 복사 때문에 메모리 할당, 해제까지 덤(?)으로 합니다)
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    19
    프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
    가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
    추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
    표 1 ALM의 3대 구성 요소
    < 그림 4-2. 원본을 버리는 복사 >
    C++0x에서는 이런 불필요한 복사를 방지하는 기능을 제공합니다. 그래서 이전보다 성능적인 측면에서
    의 개선이 이루어졌습니다. 불필요한 복사를 방지하여 성능 개선을 이루게 된 것은“우측 값 참조
    (RValue Reference)”덕택입니다.
    C++0x에서는“우측 값 참조”에 의해서 복사가 아닌 메모리 상의 이동을 할 수 있어서 메모리 할당, 복
    사, 해제를 줄일 수 있어서 프로그램의 성능 향상을 얻을 수 있습니다.
    우측 값 참조가 간단한 개념은 아니라서 쉽게 이해가 안 될 수도 있지만 제가 처음부터 하나하나 자세
    하게 설명할테니 저의 설명을 지금부터 잘 따라오시면 쉽게 이해할 수 있습니다.
    4.2 좌측 값(LValue)과 우측 값(RValue)
    “우측 값 참조”를 간단하게 정의하면 우측 값의 참조라고 할 수 있습니다. 그래서 우측 값 참조를 알기
    위해서는 필연적으로“우측 값”이 무엇을 뜻하는지 알아야 합니다.
    이 글을 보는 분들은 C++에서의 좌측 값과 우측 값에 대해서 어떻게 알고 계실지 궁금합니다. 아마 대
    부분 좌측 값은“식의 왼쪽에 있는 것”, 우측 값은“식의 오른쪽에 있는 것”이라고 생각하지 않을까 생
    각합니다. 이와 같이 우측 값과 좌측 값을 나누는 것은 C 언어라면 맞습니다.
    < 그림 4-3. C 언어에서의 좌측 값과 우측 값 >
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    20
    <그림 4-3>처럼 좌측 값과 우측 값을 나누는 것은 C 언어에서는 맞지만 C++에서는 틀립니다. C와
    C++은 좌측 값과 우측 값 정의가 서로 다릅니다.
    C++03의 사양서 3.10/1절에서 좌측 값과 우측 값에 대해서“모든 식은 좌측 값 또는 우측 값이다. 중
    요한 것은 좌측 값 또 우측 값이라고 말하는 성질은 식과 관련된 것으로 오브젝트에 대한 것이 아니다”
    라고 합니다.
    예를 들면 *ptr, ptr[index], ++x 등은 모두 좌측 값입니다. 우측 값은 그것이 존재하는 완전식이 끝나
    는 시점에서( ;이 있는 위치) 사라져 버리는 임시 값입니다. 우측 값은 예를 들면 1729, x+y,
    std::string“( C++”) 또한 x++ 등입니다.
    즉, 식이 끝난 후 계속 존재하는 값은 좌측 값, 식이 끝나면 존재하지 않는 값은 임시 값은 우측 값입니다.
    왠지 좀 확실하게 와닿지 않으시죠? 프로그래머에게는 글 보다는 코드가 가장 확실하죠. 아래의 코드로
    표현한 그림을 보시면 확실하게 이해할 수 있을 것입니다. =
    < 그림 4-4. 좌측 값과 우측 값 예 >
    이제 좌측 값과 우측 값이라는 것에 대해서 구분할 수 있을 테니 이번 장의 주제인 우측 값 참조에 대
    해서 본격적으로 들어갑니다.
    4.3 좌측 값 참조와 우측 값 참조
    C++에는 참조라는 라는 것이 있습니다. 사용 방법은‘&’을 사용합니다.
    < 그림 4-5. refA는 변수 a를 참조 >
    refA는 변수 A를 참조합니다. 이후 refA의 값을 변경하면 refA가 참조하고 있는 변수 A의 값이 변경됩
    니다. 즉 refA는 변수 A를 가리키고 있는 것입니다.
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    21
    프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
    가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
    추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
    표 1 ALM의 3대 구성 요소
    이렇게‘&’을 사용한 참조를 정확하게는‘LValue Reference’라고 부릅니다. LValue Reference는
    C++ 98 때부터 있는 것으로 지금 저희가 잘 알고 자주 사용하고 있는 것입니다.
    C++0x에는 새롭게 RValue Reference라는 것이 생겼습니다.
    사용 방법은 기존의 참조와 비슷하여 참조가‘&’을 사용했듯이‘&&’를 사용합니다.
    <코드>
    int&& RrefA = 119;
    </코드>
    우측 값 참조는 외견 상으로는 좌측 값 참조에 비해서‘&’을 하나 더 사용한다는 것만 다르지만 의미
    상 좌측 값 참조와 우측 값 참조는 다릅니다.
    < 그림 4-6. 우측 값 참조와 좌측 값 참조 사용 예 >
    <그림 4-6>에서 b)는 좌측 값 참조에 우측 값을 대입했기 때문에 에러가 발생하고, d)는 우측 값 참조
    에 좌측 값을 대입했기 때문에 에러가 발생합니다.
    이렇게 좌측 값 참조는 좌측 값을 참조해야 하고, 우측 값 참조는 우측 값을 참조해야 합니다.
    4.4 Move semantics
    4장 첫 머리에서 우측 값 참조 덕택에 불필요한 복사를 없앨 수 있다고 했는데 불필요한 복사를 없앨
    수 있는 것은 바로 우측 값 참조의 Move semantics 덕택입니다.
    Move semantics에 의해서 C++0x에서는 기존에는 없는‘move 생성자’, ‘move 대입 연산자’라는
    것이 생겼습니다. move 생성자와 move 대입 연산자는‘&&’를 사용합니다.
    클래스를 정의할 때 move 생성자나 move 대입 연산자를 정의하면 어떤 오브젝트에서 다른 오브젝트
    로 리소스를 복사가 아닌 이동 시킬 수 있습니다. 이 이동이라는 것은 오브젝트를 다른 장소의 메모리
    로 이동 시키는 것입니다.
    ‘move 생성자’, ‘move 연산자’는 암묵적으로는 만들어지지 않으면‘복사 생성자’가‘move 생성자’
    보다 우선 순위가 높고,‘ 대입 연산자’가‘move 대입 연산자’보다 우선 순위가 높습니다.
    복사가 아닌 리소스의 이동은 STL의 vector의 크기를 키울 때 사용할 수 있습니다.
    현재의 STL vector는 새로운 요소를 삽입할 때 빈 영역이 없으면 새로운 메모리를 확보한(보통 현재
    할당된 메모리의 2배) 후 기존 요소를 복사한 후 새로운 요소를 삽입합니다. 그러나 C++0x에서는
    Move semantics에 의해 성능 부하가 큰 복사가 아닌 메모리 상으로의 이동을 합니다.
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    22
    < 그림 4-7. C++98/03과 C++0x에서의 vector의 크기 확장 >
    C++0x의 모든 STL에는 Move semantics가 적용됩니다. 그래서 기존의 코드를 바꾸지 않고 C++0x
    를 사용하는 컴파일러만 사용해도 성능 향상이 이루어집니다. VC 10은 우측 값 참조를 지원하므로 모
    든 STL에 Move semantics가 적용 되었습니다.
    4.5 Move 생성자와 Move 대입 연산자
    앞서 C++0x에서는 우측 값 참조에 의해‘move 생성자’와‘move 대입 연산자’라는 것이 생겼다고
    하였습니다. move 생성자와 move 대입 연산자 정의는 아래와 같습니다.
    < 코드 4-1. QuestInfo 클래스 >
    <코드>
    class QuestInfo
    {
    public:
    // 복사 생성자
    QuestInfo(const QuestInfo& quest)
    : Name(new char[quest.NameLen]), NameLen(quest.NameLen)
    {
    memcpy(Name, quest.Name, quest.NameLen);
    }
    // 대입 연산자
    QuestInfo& operator=(const QuestInfo& quest)
    {
    if (this != &quest)
    {
    if (NameLen < quest.NameLen)
    {
    // 버퍼를 확보한다
    }
    NameLen = quest.NameLen;
    memcpy(Name, quest.Name, NameLen);
    }
    return *this;
    }
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    23
    프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
    가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
    추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
    표 1 ALM의 3대 구성 요소
    // move 생성자
    QuestInfo(QuestInfo&& quest)
    : Name(quest.Name), NameLen(quest.NameLen)
    {
    quest.Name = NULL;
    quest.NameLen = 0;
    }
    // move 대입 연산자
    QuestInfo& operator=(QuestInfo&& quest)
    {
    if( this != &quest )
    {
    delete Name;
    Name = quest.Name;
    NameLen = quest.NameLen;
    quest.Name = NULL;
    quest.NameLen = 0;
    }
    return *this;
    }
    private:
    char* Name;
    int NameLen;
    };
    </코드>
    위의 <코드 4-1>에서 QuestInfo(QuestInfo&& quest)가 move 생성자, QuestInfo&
    operator=(QuestInfo&& quest)가 move 대입 연산자입니다.
    외견 상으로 기존의 복사 생성자, 대입 연산자와의 차이는 함수 파라메터에서‘&’가 아닌‘&&’을 사용
    하는 것입니다.
    4.6 Move semantics에 의한 성능 향상
    우측 값 참조를 설명할 때 이것 덕분에 프로그램의 성능이 좋아진다고 했습니다.
    왜 그럴까요? <코드 4-1>에서 복사 생성자와 Move 생성자, 대입 연산자와 Move 대입 연산자의 구현
    을 다시 한번 잘 보시기 바랍니다. 잘 보시면 왜 성능이 좋은지 바로 아실 수 있을 것입니다. 이유를 찾
    으셨나요?
    앞서 여러 번 우측 값 참조는 Move Semantics에 의해 복사가 아닌 메모리 상의 이동을 한다고 말했
    습니다. <코드 4-1>의 Move 생성자와 Move 대입 연산자는 넘겨 받은 인자를 복사하지 않고 메모리
    상의 이동을 하고 있습니다.
    < 코드 4-2. 복사 생성자와 Move 생성자 >
    <코드>
    // 복사 생성자
    QuestInfo(const QuestInfo& quest)
    : Name(new char[quest.NameLen]), NameLen(quest.NameLen)
    {
    memcpy(Name, quest.Name, quest.NameLen);
    }
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    24
    // move 생성자
    QuestInfo(QuestInfo&& quest)
    : Name(quest.Name), NameLen(quest.NameLen)
    {
    quest.Name = NULL;
    quest.NameLen = 0;
    }
    </코드>
    <코드 4-2>의 코드에서 move 생성자 정의에서 굵게 표시한 코드를 보면 복사 생성자와 달리 메모리
    주소를 대입한 것을 알 수 있을 것입니다. 이것이 바로 복사가 아닌 이동입니다.
    메모리 이동을 한 후 인자로 넘겨진 객체가 사라지더라도 메모리 파괴가 일어나지 않도록 인자로 넘겨
    진 객체에는 NULL을 대입하고 있습니다.
    이렇게 복사가 아닌 이동을 하는 것은 크기가 작은 오브젝트에서는 큰 의미가 없지만 크기가 큰 오브젝
    트에서는 그 차이가 무시할 수 없을 것입니다.
    4.7 move 생성자와 move 대입 연산자 사용 예
    move 생성자와 move 대입 연산자를 정의한 클래스는 어떻게 동작하는 예제를 통해서 보여드리겟습
    니다.
    < 코드 4-3. 복사 생성자와 대입 연산자 정의 >
    <코드>
    #include <iostream>
    using namespace std;
    class NPC
    {
    public:
    int NPCCode;
    string Name;
    NPC() { cout << “기본생성자" << endl; }
    NPC( int _NpcCode, string _Name ) { cout << “인자있는생성자" << endl; }
    NPC(NPC& other) { cout << “복사생성자" << endl; }
    NPC& operator=(const NPC& npc) { cout << “대입연산자" << endl; return *this; }
    NPC(NPC&& other) { cout << "Move 생성자" << endl; }
    NPC& operator=(const NPC&& npc) { cout << "Move 연산자" << endl; return *this; }
    };
    int main()
    {
    cout << "1" << endl;
    NPC npc1( NPC( 10,"Orge1") );
    cout << endl << "2" << endl;
    NPC npc2(11,"Orge2");
    NPC npc3 = npc2;
    cout << endl << "3" << endl;
    NPC npc4; NPC npc5;
    npc5 = npc4;
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    25
    프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
    가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
    추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
    표 1 ALM의 3대 구성 요소
    cout << endl << "4" << endl;
    NPC npc6 = NPC(12, "Orge3");
    cout << endl << "5" << endl;
    NPC npc7; NPC npc8;
    npc8 = std::move(npc7);
    getchar();
    return 0;
    }
    </코드>
    < 결과 4-3 >
    <코드 4-3>의 결과를 보면 move 생성자와 move 대입 연산자를 정의한 NPC 클래스는 우측 값을 사
    용했을 때는 move 생성자와 move 대입 연산자가 호출 됨을 알 수 있습니다. 그런데 <코드 4-3>에서
    std::move라는 이전까지 보지 못했던 함수를 사용했습니다. move 라는 이름을 사용하는 걸로 봐서
    우측 값 참조와 관련된 함수일 것 같지 않나요?
    4.8 std::move
    <코드>
    cout << endl << "5" << endl;
    NPC npc7; NPC npc8;
    npc8 = std::move(npc7);
    </코드>
    위 코드는 <코드 4-3>에서 npc8에 npc7 이라는 좌측 값을 대입했는데 결과를 보면 move 대입 연산
    자가 사용 되었습니다. move 생성자나 move 대입 연산자는 인자가 우측 값일 때만 사용되므로 일반
    적인 대입 연산자가 호출 되야합니다. 그러나 위 코드에서는 move 대입 연산자가 호출 되었습니다. 이
    것은 바로 std::move 라는 함수를 사용하였기 때문입니다.
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    26
    std::move는 Move Semantics를 위해 이번에 새롭게 추가된 것입니다. 이것은 좌측 값(LValue)을 우
    측 값(RValue)으로 타입 캐스팅하기 위해 표준 라이브러리에서 제공하는 함수입니다.
    std::move는 아래와 같이 구현되어 있습니다.
    <코드>
    namespace std {
    template <class T>
    inline typename remove_reference<T>::type&& move(T&& x)
    {
    return x;
    }
    }
    </코드>
    위 코드를 보면 알 수 있듯이 std::move 라는 것이 뭔가 복잡한 기능을 가진 것이 아니고 단순하게 타
    입 캐스팅을 해주는 함수라는 것을 알 수 있습니다.
    4.9 VC 10에서의 STL과 우측 값 참조
    앞에서 VC 10의 STL에는 우측 값 참조가 적용되었다고 말했습니다. 이 덕분에 기존의 코드를 수정하지
    않고 VC 10으로 빌드만 해도 기존에 비해서 성능이 더 좋아집니다.
    우측 값 참조가 적용된 VC 10의 STL vector
    VC 10의 vector쪽 소스 코드를 보면 이전의 vector에는 없던 코드가 있습니다.
    <코드>
    ……………………
    #if _HAS_RVALUE_REFERENCES
    vector(_Myt&& _Right)
    : _Mybase(_Right._Alval)
    { // construct by moving _Right
    _Assign_rv(_STD forward<_Myt>(_Right));
    }
    ………
    </코드>
    위 코드를 보면 바로 아시겠죠? 네 move 생성자가 정의 되어 있습니다.
    < 코드 4-4. 우측 값 참조를 사용한 vector 사용 >
    <코드>
    vector<int> foo()
    {
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    return v;
    }
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    27
    프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
    가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
    추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
    표 1 ALM의 3대 구성 요소
    int main()
    {
    vector<int> v1 = foo();
    cout << v1[0] << endl;
    return 0;
    }
    </코드>
    <코드 4-4>를 VC 9(visual Studio 2008)와 VC 10에서 디버깅 해보면 vector의 생성자에서 서로 다
    른 부분을 호출하는 것을 볼 수 있습니다.
    VC 9에서 디버깅을 해보면 다음의 위치에서 브레이크 포인터가 걸립니다.
    < 그림 4-8. VC 9의 vector >
    위 코드를 보면 복사 생성자에서 받은 vector의 크기를 비교하여 현재 공간이 인자로 받은 vector보다
    작으면 재할당을 하고, vector에 있는 모든 요소를 복사합니다.
    < 그림 4-9. VC 10의 vector >
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    28
    보시는 바와 같이 VC 10에서는 move 생성자가 호출됩니다. 그리고 요소를 복사하지 않고 메모리 상
    의 이동을 합니다.
    또한 vector에 할당된 공간을 다 사용한 상태에서 새로운 요소를 삽입하면 기존에는 새로운 공간을 할
    당 후 저장하고 있던 모든 요소를 복사를 하지만 C++0x에서는 Move Semantics에 의해 메모리 상의
    이동을 합니다.
    STL의 string
    너무 당연하지만 string 클래스도 우측 값 참조가 잘 적용되어 있습니다.
    <코드>
    string msg1(“Error”);
    string msg2(“- Network”);
    string msg3(“: Accept”);
    string Msg = msg1 + ““ + msg2 + ““ + msg3;
    </코드>
    string에 만약 우측 값 참조가 적용되지 않았다면 operator+()가 호출될 때마다 임시 문자열이 생성되
    어 동적 메모리 할당과 복사가 일어나지만 우측 값 참조를 적용하면 불 필요한 동적 메모리 할당과 해
    제 그리고 복사를 제거할 수 있습니다.
    4.10 우측 값 참조를 사용할 때 주의할 점
    우측 값 참조를 사용하면 Move Semantics에 의해 의도하지 않은 버그가 발생할 수 있습니다.
    < 그림 4-10. 좌측 값이 우측 값으로 사용된 이후 >
    <그림 4-10>에서 npc7은 좌측 값이지만 std::move 함수에 의해서 우측 값으로 사용되었습니다.
    move 대입 연산자는 메모리 상의 이동을 하므로 npc7이 동적 메모리를 사용한 멤버를 가지고 있다면
    std::move를 사용한 이후의 npc7의 값은 유효하지 않을 것입니다.
    이런 문제는 STL에 우측 값 참조가 적용 되었으므로 vector에서도 이런 문제가 발생합니다.
    < 코드 4-5. Vector의 대입 문제 >
    <코드>
    int main()
    {
    vector<int> v1;
    v1.push_back(10);
    v1.push_back(12);
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    29
    프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
    가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
    추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
    표 1 ALM의 3대 구성 요소
    vector<int> v2 = std::move(v1);
    cout << v1.size() << endl;
    cout << v2.size() << endl;
    return 0;
    }
    </코드>
    < 결과 4-5 >
    <코드 4-5>에서 v1을 v2에서 move 생성자를 사용하기 위해서 std::move()를 사용했습니다.
    std::move는 메모리 이동을 한다고 했습니다. 그래서
    <코드>
    vector<int> v2 = std::move(v1);
    </코드>
    이 후에는 v1의 크기는 0이 됩니다.
    만약 우측 값 참조의 Move Semantics를 잘 이애하지 못한 상태에서 우측 값 참조를 사용한다면아주
    골치 아픈 버그를 만들 수 있습니다. 우측 값 참조에서 사용하는 우측 값은 임시 값이라는 것을 잘 기
    억하고 우측 값 참조를 사용해야 합니다.
    4.11 우측 값 참조와 좌측 값 참조의 함수 오버로드
    우측 값 참조와 좌측 값 참조는 타입이 다르므로 함수 오버로드를 적용할 수 있습니다.
    < 코드 4-6. 함수 오버로드 >
    <코드>
    struct ITEM {};
    void GetItem( ITEM& item )
    {
    cout << "LValue 참조" << endl;
    }
    void GetItem( ITEM&& item )
    {
    cout << "RValue 참조" << endl;
    }
    int main()
    {
    ITEM item;
    GetItem( item );
    GetItem( ITEM() );
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    30
    getchar();
    return 0;
    }
    </코드>
    < 결과 4-6 >
    4.12 우측 값 참조는 우측 값이 아니다
    우측 값은 임시 값입니다. 그러나 우측 값 참조는 임시 값이 아닙니다.
    우측 값을 참조하여 우측 값 참조가 되었더라도 우측 값 참조는 우측 값이 아닙니다.
    그래서 우측 값 참조는 우측 값 참조로 초기화 할 수 없습니다.
    < 그림 4-11. 우측 값 참조의 성공과 실패 예 >
    4.13 Perfect Forwarding
    앞선 설명에서 우측 값 참조를 사용할 때 주의할 점을 이야기 했습니다. 좌측 값이 우측 값이 되어 우
    측 값 참조로 사용되면 그 좌측 값은 보증할 수 없게 됩니다.
    템플릿 프로그래밍에서는 인자 추론이라는 것이 있는데 이 인자 추론에 의해서 좌측 값과 우측 값을 파
    라미터로 사용한 함수가 원하는대로 호출되지 않는 문제가 발생합니다.
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    31
    < 코드 4-7. >
    <코드>
    #include <iostream>
    struct X {};
    void func( X&& t )
    {
    std::cout << "RValue" << std::endl;
    }
    void func( X& t )
    {
    std::cout << "LValue" << std::endl;
    }
    template<typename T>
    void foo(T&& t)
    {
    func( t );
    }
    int main()
    {
    X x;
    foo(x);
    foo( X() );
    getchar();
    return 0;
    }
    </코드>
    < 결과 4-7 >
    <코드 4-7>의 결과를 보면 인자 추론을 할 때 우선 순위에 의해서 좌측 값을 파라미터로 사용한 함수
    가 모두 호출 되었습니다.
    이 문제를 해결하기 위해 foo 함수에서 std::move를 사용 하였습니다.
    < 코드 4-8 >
    <코드>
    #include <iostream>
    struct X {};
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    32
    void func( X&& t )
    {
    std::cout << "RValue" << std::endl;
    }
    void func( X& t )
    {
    std::cout << "LValue" << std::endl;
    }
    template<typename T>
    void foo(T&& t)
    {
    func( std::move(t) );
    }
    int main()
    {
    X x;
    foo(x);
    foo( X() );
    getchar();
    return 0;
    }
    </코드>
    < 결과 4-8 >
    std::move를 사용하여 foo 함수에서 우측 값 함수를 호출하도록 하였습니다.
    그러나 여기에서도 문제가 있습니다. std::move를 사용하는 바람에 foo 함수에 좌측 값을 전달해도 우
    측 값 참조로 사용됩니다. 앞서 이야기 했지만 좌측 값이 우측 값 참조로 사용되면 그 좌측 값을 보증
    하지 못합니다. <코드 4-8>의 foo 함수는 의도하지 않은 버그를 발생할 수도 있습니다.
    이런 문제는 std::forward 라는 표준 라이브러리에서 제공하는 함수를 사용하여 해결할 수 있습니다.
    std::forward도 std::move와 같이 타입 캐스팅을 해주는 함수입니다.
    std::forward 함수는 좌측 값은 좌측 값으로, 우측 값은 우측 값으로 캐스팅 해 줍니다.
    <코드 4-7>과 <코드 4-8>의 문제를 std::forward 함수를 사용하여 아래와 같이 해결했습니다.
    < 코드 4-9. std::forward 사용 예 >
    <코드>
    #include <iostream>
    struct X {};
    void func( X&& t )
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    33
    {
    std::cout << "RValue" << std::endl;
    }
    void func( X& t )
    {
    std::cout << "LValue" << std::endl;
    }
    template<typename T>
    void foo(T&& t)
    {
    func( std::forward<T>(t) );
    }
    int main()
    {
    X x;
    foo(x);
    foo( X() );
    getchar();
    return 0;
    }
    </코드>
    < 결과 4-9 >
    std::forward는 아래와 같이 구현 되어 있습니다.
    <코드>
    namespace std {
    template <class T, class U,
    class = typename enable_if<
    (is_lvalue_reference<T>::value ?
    is_lvalue_reference<U>::value :
    true) &&
    is_convertible<typename remove_reference<U>::type*,
    typename remove_reference<T>::type*>::value
    >::type>
    inline T&&
    forward(U&& u)
    {
    return static_cast<T&&>(u);
    }
    }
    </코드>
    Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
    34
    4.14 핵심 요약
    1. 식이 끝난 후 계속 존재하는 값은 좌측 값, 식이 끝나면 존재하지 않는 값은 임시 값은 우측 값
    2.‘ &’을 사용한 참조를 정확하게는‘LValue Reference’라고 부른다. 사용 방법은 기존의 참조와 비
    슷하여 참조가‘&’을 사용했듯이‘&&’를 사용
    3. 좌측 값 참조는 좌측 값을 참조하고, 우측 값 참조는 우측 값을 참조한다.
    4. 불필요한 복사를 없앨 수 있는 것은 바로 우측 값 참조의 Move semantics 덕택. Move
    semantics에 의해서 C++0x에서는 기존에 없는‘move 생성자’, ‘move 대입 연산자’라는 것이
    생겼음
    5.‘ move 생성자’,‘ move 연산자’는 암묵적으로는 만들어지지 않으면‘복사 생성자’가‘move 생성
    자’보다 우선 순위가 높고,‘ 대입 연산자’가‘move 대입 연산자’보다 우선 순위가 높다.
    6. 표준 라이브러리에서 제공하는 std::move를 사용하면 좌측 값을 우측 값으로 타입 캐스팅 할 수 있다.
    7. 우측 값 참조와 좌측 값 참조는 타입이 다르므로 함수 오버로드를 적용할 수 있다.
    8. 우측 값 참조는 우측 값이 아니다.
    9. std::forward 함수는 좌측 값은 좌측 값으로, 우측 값은 우측 값으로 캐스팅 해 준다.
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    35
    람다는 람다 함수 또는 이름 없는 함수라고 부르며 그 성질은 함수 오브젝트와 같습니다. 규격
    에서는 람다는 특별한 타입을 가지고 있다고 합니다. 그렇지만 decltype나 sizeof에서는 사용
    할 수 없습니다.
    C++0x는 람다 덕택에 C++의 표현력이 이전보다 훨씬 더 증대 되었습니다.
    5.1 C#과 람다
    C#이나 동적 프로그래밍 언어를 공부 하신 분들은 람다에 대해서 들어보셨을 것입니다. 람다를 잘
    모르는 분들을 위해서 현재 가장 쉽게 람다를 접할 수 있는 C#을 통해서 람다의 사용 예를 들어 보겠
    습니다.
    C#에서의 람다
    람다는 식과 문을 포함하여 대리자나 식 트리 형식을 만드는데 사용할 수 있는 익명함수입니다.
    형식은 다음과 같습니다.
    입력 매개 변수 => 식 or 문
    람다는 주로 어떤 라이브러리의 식과 결합해서 사용할 식을 만들 때 사용합니다. 람다가 없다면 다른
    식과 결합하기 위해서는 따로 함수를 만들어서 사용해야 하므로 거추장스러워 지는데 람다를 사용하면
    아주 간단하게 구현할 수 있습니다.
    < 코드 5-1. 이름 중 문자 길이가 TextLength 보다 작은 이름 >
    <코드>
    string[] MobNames = { "Babo", "Cat", "Orge", "Tester", "CEO" };
    int TextLength = 4;
    // 람다 식 사용
    var ShortNames1 = MobNames.Where(MobName => MobName.Length < TextLength);
    // foreach 사용
    List<string> ShortNames2 = new List<string>();
    foreach(string MobName in MobNames)
    {
    if (MobName.Length < TextLength)
    {
    ShortNames2.Add(MobName);
    }
    }
    </코드>
    5. 람다 (lambda)
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    36
    <코드 5-1>을 보면 람다를 사용하여 한 줄로 끝나는 것을 람다를 사용하지 않으면 List 컨테이너를 생
    성하고 foreach 구문을 사용하여 이름을 하나씩 조사하여 List 컨테이너에 추가해야 되는 코드가 필요
    해집니다.
    C#에서 람다가 가장 유용하게 사용되는 부분은 LINQ 일겁니다.
    만약 LINQ에서 람다를 사용하지 않게 된다면 LINQ를 사용하기가 꽤 힘들어질 것입니다.
    5.2 C++에서 STL 알고리즘을 사용할 때 불편했던 점
    기존의 C++에서 STL의 find_if, sort 등의 알고리즘을 사용할 때 특정 조건자를 사용하기 위해서는 펑
    터(functer)를 만들어야 합니다. 그런데 이것 때문에 따로 펑터를 만드는 것이 귀찮아서 보통 그냥 따로
    함수를 만들어서 구현하기도 합니다.
    < 코드 5-2. 펑터를 사용한 find_if 알고리즘 >
    <코드>
    #include <iostream>
    #include <algorithm>
    #include <vector>
    using namespace std;
    class User
    {
    public:
    User() : m_bDie(false) {}
    ~User() {}
    void SetDie() { m_bDie = true; }
    bool IsDie() { return m_bDie; }
    private:
    bool m_bDie;
    };
    struct FindDieUser
    {
    bool operator() (User& tUser) const { return tUser.IsDie(); }
    };
    int main()
    {
    vector< User > Users;
    User tUser1; Users.push_back(tUser1);
    User tUser2; tUser2.SetDie(); Users.push_back(tUser2);
    User tUser3; Users.push_back(tUser3);
    vector< User >::iterator Iter;
    Iter = find_if( Users.begin(), Users.end(), FindDieUser() );
    return 0;
    }
    </코드>
    <코드 5-2>는 유저들 중 죽은 유저를 찾기 위해 find_if 알고리즘을 사용했는데 이것 때문에 struct로
    펑터를 만들어야 합니다. 그런데 펑터를 만들지 않고 그냥 간단하게 기술할 수 있으면 좋겠죠?
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    37
    혹시 <코드 5-1>에서 C#에서는 람다 식으로 쉽게 구현했던 것을 보았는데 <코드 5-2>의 find_if 알고
    리즘에서도 이런 것을 사용하면 좋지 않을까요? C++0x에서 람다가 새로 생겼습니다.
    5.3 C++에서의 람다 사용 법
    5.3.1 문법
    람다의 문법은 아래와 같습니다.
    <코드>
    int main()
    {
    [] // lambda capture
    () // 함수의 인수 정의
    {} // 함수의 본체
    () // 함수 호출;
    }
    </코드>
    아래는 람다를 사용한 간단한 예입니다.
    <코드>
    int main()
    {
    []{ std::cout << “Hello, TechDay!”<< std::endl; }();
    }
    <코드>
    5.3.2 람다를 변수에 대입
    auto를 사용하면 람다를 변수에 대입할 수 있습니다.
    < 코드 5-3. 람다를 변수에 대입 >
    <코드>
    #include <iostream>
    int main()
    {
    auto func = [] { std::cout << "Hello, TechDay!" << std::endl; };
    func();
    getchar();
    return 0;
    }
    </코드>
    < 결과 5-3 >
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    38
    5.3.3 람다를 함수의 인자로 사용하기
    템플릿 프로그래밍에서 함수의 인자로 람다를 사용할 수 있습니다.
    < 코드 5-4. 함수의 인자로 사용 >
    <코드>
    #include <iostream>
    template< typename Func >
    void Test( Func func )
    {
    func();
    }
    int main()
    {
    auto func = [] { std::cout << "Gunz2 is Greate Game!" << std::endl; };
    Test( func );
    getchar();
    return 0;
    }
    </코드>
    < 결과 5-4 >
    5.3.4 람다의 파라미터
    람다는 일반 함수처럼 파라미터를 정의할 수 있습니다.
    < 코드 5-5. 파라미터 사용 >
    <코드>
    #include <iostream>
    int main()
    {
    auto func = []( int n ) { std::cout << "Number : " << n << std::endl; };
    func( 333 );
    func( 7777 );
    getchar();
    return 0;
    }
    </코드>
    < 결과 5-5 >
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    39
    5.3.5 반환 값 넘기기
    람다는 당연하게 반환 값을 넘길 수도 있습니다. 반환 값의 타입은 명시적으로 지정할 수도 있고, 암묵
    적으로 타입을 추론할 수 있습니다.
    < 코드 5-6. 반환 값 사용 >
    <코드>
    int main()
    {
    auto func1 = [] { return 3.14; };
    auto func2 = [] ( float f ) { return f; };
    auto func3 = [] () -> float{ return 3.14; };
    float f1 = func1();
    float f2 = func2( 3.14f );
    float f3 = func3();
    return 0;
    }
    </코드>
    <코드 5-6>을 컴파일하면 아래와 같은 경고가 표시 됩니다.
    < 그림 5-1. <코드 5-6>의 컴파일 경고 >
    <그림 5-1>의 경고 내용을 보면 func1가 반환하는 3.14은 double 타입으로 추론되었고, func2는 float
    타입의 파라미터를 반환하고 있어서 float로 추론되었습니다. func3는 반환 타입을 float로 명시적으로
    지정하였기 때문에 3.14를 반환하지만 func1과 같이 dobule이 아닌 float 타입으로 반환 되었습니다.
    반환 타입을 명시적으로 반환 할때는 <코드 5-6>의 func3 처럼
    -> 반환타입
    으로 지정합니다.
    5.4 캡쳐
    람다를 사용할 때 람다 외부에 정의되어 있는 변수를 람다 내부에서 사용하고 싶을 때는 그 변수를 캡
    쳐(Capture)합니다.
    캡쳐는 참조나 복사로 전달이 가능합니다. 참조를 사용하는 경우는‘&’을 사용하고, 복사로 전달할 때
    는 그냥 변수 이름을 기술합니다.
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    40
    람다 표현의
    <코드>
    [](파라메터) { 식 }
    </코드>
    에서 앞의‘[]’사이에 캡쳐 할 변수를 기술합니다.
    참조로 캡쳐
    <코드 5-7>은 람다 외부의 변수를 참조로 캡쳐하고 있습니다.
    < 코드 5-7. lambda에서 캡쳐 사용 >
    <코드>
    int main()
    {
    vector< int > Moneys;
    Moneys.push_back( 100 );
    Moneys.push_back( 4000 );
    Moneys.push_back( 50 );
    Moneys.push_back( 7 );
    int TotalMoney1 = 0;
    for_each(Moneys.begin(), Moneys.end(), [&TotalMoney1](int Money) {
    TotalMoney1 += Money;
    });
    cout << "Total Money 1 : " << TotalMoney1 << endl;
    return 0;
    }
    </코드>
    < 결과 5-7 >
    람다 식이 외부에 있는 TotalMoney1 변수를 참조로 캡쳐하여 Moneys에 있는 값을 모두 더하고 있습
    니다.
    <코드 5-8>과 같이 포인터 변수를 전달할 수도 있습니다.
    < 코드 5-8. 캡쳐로 포인터 전달 >
    <코드>
    ………….
    int TotalMoney2 = 0;
    int* pTotalMoney2 = &TotalMoney2;
    for_each(Moneys.begin(), Moneys.end(), [pTotalMoney2](int Money) {
    *pTotalMoney2 += Money;
    });
    cout << "Total Money 2 : " << TotalMoney2 << endl;
    ………..
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    41
    </코드>
    < 결과 5-8 >
    복사로 캡쳐
    그럼 TotalMoney1 변수를 값으로 전달하면 어떻게 될까요?
    <코드>
    for_each(Moneys.begin(), Moneys.end(), [TotalMoney1](int Money) {
    TotalMoney1 += Money;
    });
    </코드>
    아래와 같은 컴파일 에러가 발생합니다.
    “error C3491: 'TotalMoney1': a by-value capture cannot be modified in a non-mutable
    lambda”
    만약 꼭 복사로 캡쳐한 변수를 람다 내부에서 변경을 해야한다면 mutable 키워드를 사용하면 에러 없
    이 컴파일 할 수 있습니다.
    <코드>
    [=]() mutable{ std::cout << x << std::endl; x = 200; }();
    </코드>
    단 컴파일은 되지만 람다 내부에서 변경한 외부 변수의 값은 람다를 벗어나면 람다 내부에서 변경하기
    전의 원래의 값이 됩니다.
    복수의 변수 캡쳐
    <코드 5-7>에서는 하나의 변수만을 캡쳐했지만 복수의 변수를 캡쳐하는 것도 가능할까요? 네 당연히
    가능합니다.
    ‘[]’사이에 캡쳐 할 변수를 선언하면 됩니다.
    <코드>
    [ &Numb1, &Numb2 ]
    </코드>
    그럼‘[&]’로 하면 어떻게 될까요? 이렇게 하면 람다 식을 정의한 범위 내에 있는 모든 변수를 캡쳐할
    수 있습니다. 또 람다 외부의 모든 변수를 복사하여 캡쳐할 때는 [=]을 사용합니다.
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    42
    < 코드 5-9. 람다 외부의 모든 변수를 참조로 캡쳐하기 >
    <코드>
    int main()
    {
    vector< int > Moneys;
    Moneys.push_back( 100 );
    Moneys.push_back( 4000 );
    Moneys.push_back( 1001 );
    Moneys.push_back( 7 );
    int TotalMoney1 = 0;
    int TotalBigMoney = 0;
    // Money가 1000 보다 크면 TotalBigMoney에 누적합니다.
    for_each(Moneys.begin(), Moneys.end(), [&](int Money) {
    TotalMoney1 += Money;
    if( Money > 1000 ) TotalBigMoney += Money;
    });
    cout << "Total Money 1 : " << TotalMoney1 << endl;
    cout << "Total Big Money : " << TotalBigMoney << endl;
    return 0;
    }
    </코드>
    < 결과 5-9 >
    default 캡쳐
    람다 외부의 모든 변수를 참조(또는 복사)로 참조하고 일부는 복사(또는 참조)로 캡쳐할 수 있습니다. 그
    러나 같은 변수를 캡쳐하거나 default 캡쳐한 일부 변수를 같은 방식(참조 또는 복사)으로 캡쳐할 수 없
    습니다.
    < 그림 5-2. default 캡쳐 >
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    43
    5.5 클래스에서 람다 사용
    클래스의 멤버 함수 내에 람다 식을 정의하고, 이 람다 식에서 해당 클래스의 멤버를 호출 할 수 있습
    니다. 클래스 멤버 내의 람다 식은 해당 클래스에서는 friend로 인식하므로 람다 식에서 private 멤버
    의 접근도 할 수 있습니다. 그리고 클래스의 멤버를 호출할 때는‘this’를 캡쳐합니다.
    < 코드 5-10. 클래스 멤버 호출 >
    <코드>
    class NetWork
    {
    public:
    NetWork()
    {
    SendPackets.push_back(10);
    SendPackets.push_back(32);
    SendPackets.push_back(24);
    }
    void AllSend() const
    {
    for_each(SendPackets.begin(), SendPackets.end(), [this](int i){ Send(i); });
    }
    private:
    vector< int > SendPackets;
    void Send(int PacketIndex) const
    {
    cout << "Send Packet Index : " << PacketIndex << endl;
    }
    };
    int main()
    {
    NetWork().AllSend();
    return 0;
    }
    </코드>
    < 결과 5-10 >
    <코드 5-10>의 Network 클래스의 멤버 함수 AllSend()에서 람다를 사용했습니다.
    <코드>
    for_each( SendPackets.begin(), SendPackets.end(),
    [this](int i){ Send(i); });
    </코드>
    위와 같이‘[]’사이에‘this’를 기술하였고, Network 클래스의 private 멤버 함수인 Send를 호출하고
    있습니다. 당연히 멤버 변수도 호출 할 수 있습니다.
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    44
    5.6 STL의 find_if에서 람다 사용
    그럼 <코드 5-2>에서 펑터를 정의하여 사용했던 것을 람다를 사용하는 것으로 바꾸어 보겠습니다.
    < 코드 5-11. find_if 알고리즘에서 lambda 사용 >
    <코드>
    #include <iostream>
    #include <algorithm>
    #include <vector>
    using namespace std;
    class User
    {
    public:
    User() : m_bDie(false) {}
    ~User() {}
    void SetIndex(int index) { m_Index = index; }
    int GetIndex() { return m_Index; }
    void SetDie() { m_bDie = true; }
    bool IsDie() { return m_bDie; }
    private:
    int m_Index;
    bool m_bDie;
    };
    int main()
    {
    vector< User > Users;
    User tUser1; tUser1.SetIndex(1); Users.push_back(tUser1);
    User tUser2; tUser2.SetIndex(2); tUser2.SetDie();
    Users.push_back(tUser2);
    User tUser3; tUser3.SetIndex(3); Users.push_back(tUser3);
    vector< User >::iterator Iter;
    Iter = find_if( Users.begin(), Users.end(), [](User& tUser) -> bool { return
    true == tUser.IsDie(); } );
    cout << "found Die User Index : " << Iter->GetIndex() << endl;
    return 0;
    }
    </코드>
    < 결과 5-11 >
    find_if 알고리즘을 사용하여 죽은 유저를 찾기 위해서 <코드 5-2>에서는 펑터를 정의한 후 사용하였지만
    <코드>
    struct FindDieUser
    {
    bool operator() (User& tUser) const { return tUser.IsDie(); }
    };
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    45
    Iter = find_if( Users.begin(), Users.end(), FindDieUser() );
    </코드>
    <코드 5-11>은 람다를 사용하여 한 줄로 간단하게 끝내버렸습니다.
    <코드>
    Iter = find_if( Users.begin(), Users.end(), [](User& tUser) -> bool {
    return true == tUser.IsDie();
    } );
    </코드>
    예전에는 알고리즘을 사용하려면 펑터를 정의해야 되기 때문에 귀찮아서 알고리즘 사용이 꺼려졌는데
    람다 덕분에 알고리즘 사용이 너무나 간편해졌습니다. 제 생각에 람다를 가장 자주 사용하는 부분이 바
    로 STL의 알고리즘을 사용할 때가 아닐까 생각합니다.
    <참고>
    STL의 알고리즘 사용과 성능
    컨테이너의 요소를 검색을 할 때 STL의 알고리즘을 사용하는 것이 본인이 직접 for 문이나 while 문을
    사용하여 순회해서 찾는 것 보다 성능이 더 좋습니다. 그런데 알고리즘을 사용하려면 펑터를 정의해야
    하기 때문에 귀찮아서 알고리즘을 사용하지 않는 경우가 적지 않습니다.
    그러나 VC 10부터는 람다를 사용할 수 있으므로 STL의 알고리즘을 쉽게 사용할 수 있습니다. 람다를
    사용하는 것은 펑터를 사용하는 것과 같으므로 같은 이점을 얻을 수 있습니다.
    티티에프님의 Vector Container Iterating 속도 비교( http://npteam.net/775 )
    </참고>
    5.7 람다 식을 STL 컨테이너에 저장
    람다 식을 STL의 function을 사용하여 STL 컨테이너에 저장할 수 있습니다.
    아래는 int를 반환하는 람다 식을 vector에 저장하여 사용하는 것입니다.
    < 코드 5-12. 람다 식을 vector 컨테이너에 저장 >
    <코드>
    #include <iostream>
    #include <algorithm>
    #include <functional>
    #include <vector>
    using namespace std;
    int main()
    {
    vector<function<int()>> v;
    v.push_back( [] { return 100; } );
    v.push_back( [] { return 200; } );
    cout << v[0]() << endl;
    cout << v[1]() << endl;
    }
    </코드>
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    46
    < 결과 5-12 >
    5.8 람다를 반환하는 함수
    5.7에서 람다를 vector에 저장하기 위해 STL의 function을 사용하였는데 이것을 또 다른 방법으로 사
    용하면 람다를 반환 값으로 사용하는 함수에서도 사용할 수 있습니다.
    < 코드 5-13. 람다를 반환하는 함수 >
    <코드>
    #include <iostream>
    #include <functional>
    #include <string>
    std::function< void() > f()
    {
    std::string str("lambda");
    return [=] { std::cout << "Hello, " << str << std::endl; };
    }
    int main()
    {
    auto func = f();
    func();
    f()();
    return 0;
    }
    </코드>
    5.9 람다에서의 재귀
    람다는 재귀도 가능합니다.
    < 코드 5-14. 람다의 재귀 >
    <코드>
    int main()
    {
    function<int(int)> fact = [&fact](int x) {
    return x == 0 ? 1 : x * fact(x - 1);
    };
    cout << fact(3) << endl;
    }
    </코드>
    fact 변수를 참조로 넘겨서 재귀를 하고 있습니다.
    Visual C++ 10과 C++0x 5. 람다 (lambda)
    47
    지금까지 람다의 다양한 사용 방법을 설명 하였습니다. 람다를 사용하여 C++로 다양하게 표현할 수 있
    는 것을 보았을 것입니다. 람다 덕택에 C++의 표현력이 이전보다 훨씬 더 좋아졌습니다.
    참고로 Visual C++ 10에서는 Concurrency Runtime(ConRT)라는 것이 새로 추가 되었습니다. 이것
    은 템플릿으로 만들어진 것으로 람다가 많이 사용 되었다고 합니다.
    5.10 핵심 요약
    1. 람다는 람다 함수 또는 이름 없는 함수라고 부릅며 함수 오브젝트이다.
    2. 규격에서 람다는 특별한 타입을 가지고 있다고 한다. 단 decltype나 sizeof에서는 사용 불가
    3. 람다를 정의한 곳의 외부 변수를 람다 내부에서 사용하고 싶을 때는 캡쳐한다.
    4. 외부 변수를 참조 또는 복사로 캡쳐할 수 있다.
    5. 클래스에서도 람다를 사용할 수 있다. 클래스는 람다를 friend로 인식한다.
    6. 람다 덕택에 C++의 표현력이 이전보다 훨씬 더 증대 되었다.
    6. decltype
    Visual C++ 10과 C++0x 6. decltype
    48
    6.1 템플릿 프로그래밍 시 문제
    템플릿 타입(type)이랑 템플릿 메타 프로그래밍 등에서 함수의 반환 형이 복잡하게 정의 되어 있는 경
    우 타입을 단순하게 기술할 수 없습니다.
    C++0x에서는 이런 문제를 풀기 위해 auto와 decltype 두 개를 제공합니다.
    decltype은 auto 처럼 식의 타입을 컴파일 할 때 결정할 수 있습니다.
    6.2 decltype 사용하기
    사용 예는 아래와 같습니다.
    < 코드 6-1. decltype 사용 예 >
    <코드>
    int Hp;
    decltype(Hp) NPCHp = 5;
    decltype(Hp + NPCHp) TotalHp;
    decltype(Hp*) pHp = &Hp;
    </코드>
    decltype(Hp) NPCHp = 5;
    는 Hp가 int 타입이므로 int NPCHp = 5로 컴파일 할 때 결정됩니다.
    또 decltype(Hp + NPCHp) TotalHp;
    는 int와 int의 덧셈이므로 int TotalHp;로 됩니다.
    decltype(Hp*) pHp = &Hp;는 int* pHp = &Hp가 됩니다.
    또한 함수의 반환 타입으로도 사용할 수 있습니다.
    < 코드 6-2. 함수의 반환 타입으로 decltype 사용 >
    <코드>
    int foo();
    decltype(foo()) value;
    </코드>
    decltype(foo()) value;는 int value;로 됩니다.
    Visual C++ 10과 C++0x 7. nullptr
    49
    nullptr은 C++0x에서 추가된 키워드로 널 포인터(Null Pointer)를 나타냅니다.
    7.1 nullptr이 필요한 이유
    C++03까지는 널 포인터를 나타내기 위해서는 NULL 매크로나 상수 0을 사용하였습니다.
    그러나 NULL 매크로나 상수 0을 사용하여 함수에 인자로 넘기는 경우 int 타입으로 추론되어 버리는
    문제가 발생하기도 합니다.
    < 코드 7-1. 함수 인자 추론 문제 >
    <코드>
    #include <iostream>
    using namespace std;
    void func( int a )
    {
    cout << "func - int " << endl;
    }
    void func( double *p )
    {
    cout << "func - double * " << endl;
    }
    int main()
    {
    func( static_cast<double*>(0) );
    func( 0 );
    func( NULL );
    getchar();
    return 0;
    }
    </코드>
    < 결과 7-1 >
    첫 번째 func 호출에서는 double* 로 캐스팅을 해서 의도했던 func이 호출 되었습니다. 그러나 두 번
    째와 세 번째 func 호출의 경우 func( doube* p ) 함수에 널 포인터를 파라미터로 넘기려고 했는데
    의도하지 않게 컴파일러는 int로 추론하여 func( int a )가 호출 되었습니다.
    바로 이와 같은 문제를 해결하기 위해서 nullptr 이라는 키워드가 생겼습니다.
    7. nullptr
    Visual C++ 10과 C++0x 7. nullptr
    50
    7.2 nullptr 구현 안
    C++0x에서 nullptr의 드래프트 문서를 보면 nullptr은 아래와 같은 형태로 구현 되어 있습니다.
    < 코드 7-2. nullptr 구현 클래스 >
    <코드>
    const class {
    public:
    template <class T>
    operator T*() const
    {
    return 0;
    }
    template <class C, class T>
    operator T C::*() const
    {
    return 0;
    }
    private:
    void operator&() const;
    } nullptr = {};
    </코드>
    7.3 nullptr 사용 방법
    사용방법은 너무 너무 간단합니다. ^^
    그냥 예전에 널 포인터로 0이나 NULL을 사용하던 것을 그대로 대처하면 됩니다.
    <코드>
    char* p = nullptr;
    </코드>
    <코드 7-1>에서 널 포인트를 파라미터로 넘겨서 func( double* p )가 호출하게 하기 위해서는
    <코드>
    func( nullptr );
    </코드>
    로 호출하면 됩니다.
    7.4 nullptr의 올바른 사용과 틀린 사용 예
    올바른 사용
    <코드>
    char* ch = nullptr; // ch에 널 포인터 대입.
    sizeof( nullptr ); // 사용 할 수 있습니다. 참고로 크기는 4 입니다.
    Visual C++ 10과 C++0x 7. nullptr
    51
    typeid( nullptr ); // 사용할 수 있습니다.
    throw nullptr; // 사용할 수 있습니다.
    </코드>
    틀린 사용
    <코드>
    int n = nullptr; // int에는 숫자만 대입가능한데 nullptr은 클래스이므로 안됩니다.
    Int n2 = 0
    if( n2 == nullptr ); // 에러
    if( nullptr ); // 에러
    if( nullptr == 0 ); // 에러
    nullptr = 0; // 에러
    nullptr + 2; // 에러
    </코드>
    VC++ 10에서는 예전처럼 널 포인터를 나타내기 위해서 0이나 NULL 매크로를 사용하지 말고 꼭
    nullptr을 사용하여 함수나 템플릿에서 널 포인터 추론이 올바르게 되어 C++을 더 효율적으로 사용하
    기 바랍니다.
    왜 nullptr 이라고 이름을 지었을까?
    nullptr을 만들 때 기존의 라이브러리들과 이름 충돌을 최대한 피하기 위해서 구글로 검색을 해보니
    nullptr로 검색 결과가 나오는 것이 별로 없어서 nullptr로 했다고 합니다.
    제안자 중 한 명인 Herb Sutter은 현재 Microsoft에서 근무하고 있는데 그래서인지 C++/CLI에서는
    이미 nullptr 키워드를 지원하고 있습니다.
    8. 참고
    Visual C++ 10과 C++0x 8. 참고
    52
    C++0x
    http://en.wikipedia.org/wiki/C%2B%2B0x
    static_assert
    http://en.wikipedia.org/wiki/C%2B%2B0x#Static_assertions
    Rvalue Reference
    http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-
    part-2.aspx
    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html
    http://www.artima.com/cppsource/rvalue.html
    http://cpplover.blogspot.com/2009/11/rvalue-reference_23.html
    http://cpplover.blogspot.com/2009/11/rvalue-reference_25.html
    lambda
    http://blogs.msdn.com/vcblog/archive/2008/10/28/lambdas-auto-and-static-assert-c-0xfeatures-
    in-vc10-part-1.aspx
    http://cpplover.blogspot.com/2009/11/lambda.html
    http://cpplover.blogspot.com/2009/12/lambda.html
    decltype
    http://blogs.msdn.com/vcblog/archive/2009/04/22/decltype-c-0x-features-in-vc10-part-3.aspx
    nullptr
    http://d.hatena.ne.jp/faith_and_brave/20071002/1191322319
    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf
    http://ja.wikibooks.org/wiki/More_C%2B%2B_Idioms/nullptr
    http://d.hatena.ne.jp/KZR/20080328/p1
    저자 : 최흥배 마이크로소프트 Visual C++ MVP
    현재 온라인 게임 개발 회사 마이에트 엔터테인먼트에서 온라인 게임의 서버 프로그램을 개발하고
    있으며, Microsoft Visual C++ MVP로도 활동하고 있습니다. 프로그래밍 언어로 C++와 C#을 좋아
    하고 요즘은 병렬 프로그래밍과 클라우드 컴퓨팅, 스마트 폰 프로그래밍에 많은 관심을 가지고 있습
    니다. 블로그(http://jacking.tistory.com/)와 트위터(@jacking75)를 통해서 게임 이야기나 프로그래밍,
    게임 개발에 대한 정보를 다른 개발자들과 공유하고 있습니다.

    한국마이크로소프트(유)
    서울특별시 강남구 대치동 892번지 포스코 센터 서관 5층 (우) 135-777
    고객지원센터 : 1577-9700, 제품홈페이지 : http://www.microsoft.com/visualstudio, 개발자포털 : http://msdn.microsoft.com

    반응형
    반응형

    오늘은 메모리에 관해 좀 재밌는 내용을 설명하겠다.
    레퍼런스 변수는 원본과 같은 메모리 공간을 가리킨다고 지난 포스트에 설명했다.
    (기억이 나지 않는다면 [제2장] C++ 기본 : Reference 포스트를 참고하시라~)

    레퍼런스 변수를 리턴값으로 쓰기 위해선 원본이 메모리상에 보존되어야 한다.
    무슨 말인고 하니... 레퍼런스 변수가 가리킨 값(원본)이 사라진다면
    레퍼런스 변수 역시 의미가 없어지기 때문이다.

    일단 정상적으로 돌아가는 아래 예제를 살펴보자.

    #include < iostream >
    using namespace std;

    int& increment(int &val) // 레퍼런스 변수로 원본을 받음
    {
     val++;
     return val;
    }

    void main()
    {
     int n = 10;
     int &ref = increment(n); // 파라미터로 레퍼런스 원본으 넘겨줌

     cout << "n : " << n << endl;
     cout << "ref: " << ref << endl;
    }

    자... 누구나 예상할 수 있듯이 결과는 11이 출력된다.
    왜냐하면 원본 변수 int n 은 레퍼런스 변수가 사용되는 동안 유지되기 때문이다.
    10의 값이 함수 안에서 ++ 연산을 통해서 11이 되었고 그것을 출력하였다.

    그럼 당연히 반대의 경우도 살펴봐야되지 않겠는가?
    지역변수를 레퍼런스 변수로 리턴하는 경우를 살펴보자.

    #include < iostream >
    using namespace std;

    int& function()
    {
     int val=10; // 지역변수가 원본이 되었다
     return val;
    }

    int& function2() // 임의로 추가한 두번 째 함수
    {
     int val2=20;
     return val2;
    }

    void main()
    {
     int &ref = function(); // 레퍼런스 변수 ref의 원본은 function()의 지역변수이다
     int &ref2 = function2();

     cout << ref << endl;
    }

    자... 무엇이 출력될 것 같은가? - 주의 : 원본 소스에서 function2()가 추가되었다 -
    언뜻 보면 ref는 function()의 지역변수 int val = 10; 로부터 참조하였으니 10을 출력할 것 같다.

    하지만 이 소스는 20을 출력한다!
    ref는 분명 function()에서 값을 받았는데... function2()의 20이 출력된다.
    우째 이런 일이...!

    자 이제부터 이유를 알아보자.
    main()이 실행되면 프로세서에 int ret와 int ret2에 대한 메모리를 할당한다 (총 8바이트)
    예를들어 ret가 메모리 8000번에, ret2가 8004번에 할당되었다고 가정한다.

    ㅁㅁㅁㅁ ㅁㅁㅁㅁ
    (ret)   (ret2)

    다음 function()이 실행되면 함수 내 지역 변수를 할당하기 위해서 추가적으로 4바이트를 할당한다
    그럼 val은 메모리 8008번에 할당된다.

    ㅁㅁㅁㅁ ㅁㅁㅁㅁ ㅁㅁㅁㅁ
    (ret)   (ret2)   (val)

    이제 ret는 레퍼런스 리턴으로 val의 위치를 참조하게 된다. 이때 가리키는 값은 10 이다.
    (지금 cout으로 출력하면 10이 출력된다)

    자~ 이제 함수 function()이 종료되고 지역번수 val이 차지하는 메모리공간 8008은 사라진다.

    ㅁㅁㅁㅁ ㅁㅁㅁㅁ
    (ret)   (ret2)

    그리고 function2()가 실행되면서 val2의 메모리가 8008에 할당된다.

    ㅁㅁㅁㅁ ㅁㅁㅁㅁ ㅁㅁㅁㅁ
    (ret)   (ret2)   (val2)

    중요한 점은 ret가 아직도 8008번을 가리키고 있다는 것이다!
    val2의 값은 20이 대입되었으니 이제 ret의 값을 출력하면 20이 출력된다.

    이러한 이유로 지역변수를 레퍼런스로 리턴할 경우 뜻하지 않은 결과를 초래할 수 있다.
    이런 버그는 참 찾기 힘드니 절.대.로. 이렇게 코딩하지 말자!

    반응형
    반응형

    추가자료 : http://blog.naver.com/leojesus?Redirect=Log&logNo=80045980042

    아이오 교육센터의 기술자료는 다양한 서적/문서를 참고해서 교육센터 자체로 만든 자료입니다.

    마음대로 퍼가셔도 되지만 꼭 출처를 밝혀 주시기 바랍니다.

    float type에 대한 고찰

    C 에서 실수 type은 아래의 2가지로 나눌수 있습니다.

    float Type : 4byte 실수를 표현 하는 타입
    double Type
    : 8byte 실수를 표현 하는 타입

    이 중에서 float type 에 대하여 역사적 배경에 기반하여 자세히 알아 보겠습니다.

    다음 C언어 소스를 컴파일 후 실행 했을 때의 예상되는 값을 머리속으로 추측 해보고 실행하여 나오는 결과 값과 어떻게 차이가 나는 지 살펴 봅시다. 실행한 결과 값이 생각한 값과 같다면 실수의 내부 구조를 정확히 이해 하고 있으므로 더 이상 읽지 않으셔도 좋습니다.

    #include <stdio.h>

    int main()

    {

    float f = 0.1f;

    float sum = 0.0f;

    int i;

    for( i=0 ; i<100000; i++ )

    sum += f;

    printf("%f\n", sum );

    return 0;

    }


    프로그램의 결과는? 예상대로 0.1 * 100000 이므로 10000 이 나와야 정상이지만 무슨 이유 때문인지 1.5정도의 오차가 있는
    9998.556641 로 출력 됩니다. 실수 표현이라 정확 할 것 같았지만 전혀 그렇지 않네요. 어떠한 이유 때문인지 실수의 내부구조를 살펴 봐야 할 것 같습니다.

    정수와 다르게 실수는 표현하려는 정보가 두가지입니다. 양수/음수를 제외 하고도 말이지요. 그 두가지 정보는 바로 정수부와 소수부라는 것입니다.

    3.14 라는 수가 있을 때 이수는

    정수부 : 3
    소수부 : 0.14

    로 나눌 수 있습니다. 과연 메모리 4byte에 이수를 어떻게 표현 해야 가장 효율적일 까요?

    초창기 방법은

    고정 소수점 방식을 사용 했습니다. 고정 소수점 방식은 상위 16bit에는 정수부를 하위 16bit에는 소수부를 표현하는 방식이었죠.
    예를 들어

    10.25 라는 숫자가 있을 때

    1. 이 수를 이진수로 바꾼다.
    10.25 => 1010.01
    2. 상위 16bit에는 1010을
    3. 하위 16bit에는 01을 표현한다.

    이를 그림으로 보자면 다음과 같습니다.

    1 0 1 0 0 1

    정수부 실수부
    그림 1) 고정소수점 방식

    위 그림을 보면 아주 합리적으로 보입니다. 정확히 메모리의 반을 정수부에 반은 실수부에 할당 하고 있으니까요.
    하지만 이런식의 표현에서는 실수가 추구하는 두가지의 중요한 목적을 간과한 것입니다.

    실수를 사용하는 목적은
    - 매우 큰수를 표현하기 위해
    - 매우 정밀한 숫자를 표현하기 위해

    위 방식을 사용했을 경우 표현 범위는 양수만을 고려한다고 해도
    범위 : 0 ~ 65536
    정밀도 : 1/65536
    볼 수 있다. 이는 매우 큰수도 아니고 매우 정밀한 수도 아니므로 실수의 사용 목적을 어느것도 만족하지 못합니다.


    이런 표현 방법은 소수점이 중앙에 고정 되어 있다 하여 고정 소수점 방식이라 부릅니다.
    그렇다면 매우 크면서도 정밀한 숫자를 표한하는 효율적인 방법은 무엇일까요?
    이런 고민은 지금 우리 뿐만 아니라 우리의 선인들의 고민이기도 했습니다. 이에 유명한 국제 표준기관인 IEEE에서 아주 기발하면서도 흥미로운 제안을 합니다.

    그 방법은 우리가 흔히 알고 있는 지수 표기법 이라는 것입니다.
    이를 컴퓨터공학에서는 부동소수점이라고 부릅니다.

    여기서 의문은 부동(
    不動) 이라는 뜻은 움직이지 않는다는 것인데 고정가 무슨차이가 있느냐는 것이죠.
    이때 부동이라는 뜻은 "움직이지 않는다" 가 아니고
    부동(
    浮動) 으로 아니부 가 아니고 뜰부를 써서 "떠서 움직이다" 라는 뜻입니다. 즉 소수점이 떠서 움직인다는 것이지요.

    10.25 라는 숫자가 있을 때

    1. 이 수를 이진수로 바꾼다.
    10.25 => 1010.01

    2. 소수점을 이동한다.
    1010.01 => 0.101001 * 24
    이를 그림으로 표현하면

    0 0 0 0 0 0 1 0 0 1 0 1 0 0 1

    부호 지수부 가수부

    그림2) 부동 소수점 표현 방법

    위에 방식으로 표현 하면

    범위 : 2-128 - 2+127
    정밀도 : 1/
    8388608

    로 표현 가능 하니 매우 큰 숫자도 표현 가능할뿐더러 매우 정밀한 숫자도 표현 가능 해졌다는 것입니다.


    하지만 완벽할 것 같은 위방법도 두가지의 문제를 안고 있습니다. 바로

    문제점 1 : 1010.01 => 0.101001 * 24 이런식의 이동은 어차피 왼쪽 1의 위치가 마지막 이동부분이므로 항상 0.1 형태로 시작한다. 그러므로 0.1까지 이동하지 말고 1.0 형태로 이동 하면 소수점의 쓸데 없는 1번의 이동을 막을수 있고 이렇게 하면 1bit의 낭비를 막을수 있다.

    문제점 2 : 지수부에는 양수도 들어가야 하지만 음수도 들어가야 한다. 이때 음수를 표현하기위해 정수처럼 2의 보수를 사용한다면
    sign bit를 따로 둔 실수에서는 중복처리라고 볼 수 있으므로 음수를 표현하기 위해 2의 보수 대신 127 바이어스 코드를 사용하여 문제를 해결한다.

    10.25 라는 숫자가 있을 때

    1. 이 수를 이진수로 바꾼다.
    10.25 => 1010.01

    2. 소수점을 이동한다.
    1010.01 => 1.01001 * 23

    이를 그림으로 표현하면

    0 1 0 0 0 0 0 1 0 0 1 0 0 1

    부호(s) 지수부(e) 가수부(m)

    그림3) IEEE 부동 소수점 표준

    메모리에는 위에 그림 처럼 저장 하고 다시 CPU로 로드 할 때는 다음 수식에 따라 로드 된다.

    (-1)s * 1.m * 2(e-127)

    위의 방식은 C언어 뿐만 아니라 C++, java, C# 등 모든 언어에서 사용하는 실수의 표준이므로 개발자하면 반드시 알아야 할 내용입니다.

    실수의 내부 구조를 알아야 하는 이유

    1. 정확한 실수의 내부구조를 알아서 논리적 에러 없는 코드 구현가능
    2. double로도 표현 불가능한 초 정밀도 프로그램 (예. 항공우주, 유전자, 건축 등 ) 제작시 실수 타입 설계 가능

    그렇다면 처음 논제로 들어 가서 0.1을 10만번 더했을 때 부동소수점으로 표현 했는데도 불구 하고 10000이라는 결과가 나오지 않았을까요?

    그렇다면 위 지식을 배경으로 0.1의 내부 구조를 분석해 봅시다.

    0.1 라는 숫자가 있을 때

    1. 이 수를 이진수로 바꾼다. 소수점 이하를 바꿀때는 해당 소수점 이하의 수에 2를 계속 곱하고 연산의 결과인 정수를 위에서 아래로 쓴다.
    0.1
    0.2
    0.4
    0.8
    1.6
    1.2
    0.4
    0.8
    1.6
    1.2
    ....

    0.1 => 0.0001100110011....
    2. 소수점을 이동한다.
    0.0001100110011.... => 1.100110011... * 2-4

    이를 그림으로 표현하면

    0 1 1 1 1 1 0 1 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0

    부호(s) 지수부(e) 가수부(m)

    그림3) 0.1의 IEEE 부동 소수점 표준

    즉 , 그림에서 알수 있듯이 0.1이 10진수에서는 유한소수이지만 2진수를 변환하게 되면 2의 배승단위가 아니므로 무한소수가 된다. 아무리 가수부를 23자리나 표현한다 해도 유효숫자가 잘려 나가기 때문에 많은 회전수를 돌리면 오차가 발생하는 것이다.

    실수 프로램시 가이드 라인)

    회전수가 많거나 정밀한 수 표현시 절대 float를 사용하지 말고 double을 사용하라. double도 초정밀도 프로그램에는 적절치 않으므로 초정밀도 관련 프로그램은 type을 생성하여 사용하라.

    따라서 처음 프로그램의 올바른 사용예는 다음과 같다.

    #include <stdio.h>

    int main()

    {

    double f = 0.1; // 0.1 상수는 double로 해석된다.

    double sum = 0.0;

    int i;

    for( i=0 ; i<100000; i++ )

    sum += f;

    printf("%lf\n", sum ); // printf 포메팅은 double인 경우 lf사용

    return 0;

    }

    결론 : 실수는 부동 소수점을 사용하고 float(32bit: s=>1, e=>8, m=>23), double(64bit: s=>1, e=>11, m=>52)의 내부구조를 가지고 있습니다. 간단한 실수 프로그램에는 float을 사용하고 정밀한 프로그램에서는 반드시 double을 초정밀도 프로그램에서는 type을 생성하여 사용해야 합니다.

    이로써 여러분은 정수와 실수를 정확하게 알게 되셨으므로 c언어의 기본인 type을 마스터 하게 되었습니다.
    다음에 진행될 연산자와 제어 구조도 계속 공부 하세요...

    출처 : C기본부터 고급까지 C 교육의 모든 것. 아이오 교육센터(www.ioacademy.co.kr)

    반응형
    반응형

    .ini 클래스

    한번 열고 read, write 를 자유롭게 사용 할 수 있도록 인터넷에 떠도는 것을 수정했다

    반응형
    반응형

    CString -> char*

    http://cafe.naver.com/woosongbitcafe/123

    <문자열변환>
    // char를 TCHAR로 변환
    mbstowcs(TCHAR* str, char* str2, 256);

    // TCHAR를 char로 변환
    wcstombs((char* str, TCHAR* str2, 256);

    // 문자열을 정수로 변환
    int Num = _ttoi(TCHAR* str);

    // 정수를 문자열로 변환
    TCHAR buf[256];
    int Num;
    _itow(Num, buf, 10); // 10은 10진법을 의미

    // Cstring 에서 wstring으로 변환
    CString cstr;
    wstring wstr(_T(""));
    wstr += cstr.GetString();

    // wstring에서 CString으로 변환
    CString cstr;
    wstring wstr(_T(""));
    cstr = wstr.c_str(); 

    반응형
    반응형

    http://kimbeast.blog.me/60036942254



    라이브러리 만들던중 문득 나중에 UNICODE로 만들게 되면.? 이라는 의문에 unicode출력 테스트를 하게 되었다.

    환경 (gcc 4.1.x, opensuse 10.2)


    #include <stdio.h>
    #include <wchar.h>
    #include <locale.h>
    #include <iostream>

    using namespace std;
    int main()
    {
    setlocale(LC_ALL, "ko_KR.utf8");

    wchar_t *wstring3 = L"한글Alpha";

    wcout << wstring3 << endl;

    return 0;
    }


    위와 같이 하니.잘 나왔다.
    그래서 라이브러리에 집어넣고 출력을 봤더니만. .아무것도 안 나오는 현상이 발생하였다.
    원인이 뭘까..고민하던 차에 아주 황당한 결과를 보게 되었다.


    #include <stdio.h>
    #include <wchar.h>
    #include <locale.h>
    #include <iostream>

    using namespace std;
    int main()
    {
    setlocale(LC_ALL, "ko_KR.utf8");

    wchar_t *wstring3 = L"한글Alpha";

    cout << wstring3 << endl;
    wcout << wstring3 << endl;


    return 0;
    }


    위와 같이 한다면..출력결과는???

    cout 의 결과만 나온다.
    wcout 의 결과는 엉망으로 나온다.

    다시 반대로..


    #include <stdio.h>
    #include <wchar.h>
    #include <locale.h>
    #include <iostream>

    using namespace std;
    int main()
    {
    setlocale(LC_ALL, "ko_KR.utf8");

    wchar_t *wstring3 = L"한글Alpha";

    wcout << wstring3 << endl;
    cout << wstring3 << endl;

    return 0;
    }


    wcout 의 결과만 나온다.
    cout 의 결과는안나온다.

    즉 라이브러리안에 있던 cout 문장이 출력에 영향을 줘서 unicode 출력이 되지 않는 것이었다.

    결론적으로 유니코드 base 로 만들려면 cout을 절대 사용하지 말란 말이 된다.
    이게 어디 쉬운일인가? 잘 통제되지 않으면.그냥 출력한다고 cout넣을텐데 말이다.
    오묘한 세계다...

    반응형

    '프로그래밍(Programming) > c++, 11, 14 , 17, 20' 카테고리의 다른 글

    .ini 파일 클래스  (0) 2012.10.31
    문자열 변환  (0) 2012.10.31
    Timer - getElapsedMilliSecond  (0) 2012.10.31
    C 언어에서 파일을 여는 함수 입니다. W/R 모드  (0) 2012.10.31
    함수포인터  (0) 2012.10.31
    반응형

    Timer

    전산 2004/03/02 14:40
    타이머 문제로 골몰하다 멀티미디어 라이브러리의 바이너리를 뒤적거리기로 했다. 한참동안 winmm.lib 의 네이티브 코드를 뒤지다가 재미난 것을 한가지 발견. 일단 해당 부분을 뜯어 간단히 인라인 어셈으로 옮겨 보았다.

    inline unsigned __int64 getMisteryCount()
    {
    __asm
    {
    mov edx,dword ptr ds:[7FFE000Ch]
    mov eax,dword ptr ds:[7FFE0008h]
    }
    }


    아무런 문서도 없이 바이너리를 뒤지다 얻어낸 정보인지라 저 주소가 무엇을 의미하는지는 나도 모른다. 구글에도 없는것을 보니 완전한 un-documented feature 같다. 어쨌든 저 주소에는 1/10000 초 단위의 시간이 저장되어 있다. 물론 아래와 같이 10000 을 나누어 milli-second 단위로 변환해서 사용하는것도 가능하다.

    inline unsigned int getElapsedMilliSecond()
    {
    return static_cast< unsigned int >( getMisteryCount()/10000 );
    }


    mov 를 두번 호출할 뿐인지라 속도는 경이적이다. 빠르지만 최악의 정밀도를 보여주는 GetTickCount() 보다도 4배정도 빠르다. 아무일도 하지 않고 루프만 돈 noproc() 에 거의 근접한다.



    getMisteryCount() 와 getElapsedMilliSecond() 의 속도차이의 이유는 64-bit div 연산에 있다. 32-bit 프로세서에서 __int64 나누기를 하려면 수십개의 인스트럭션이 필요하다.

    테스트 결과 getMisteryCount() 는 Win95/98/Me 등에선 동작하지 않는다. XP , 2000, 2003 등 NT 커널 기반 OS 에서만 돌아가는 듯 하다. 어쨌건 퍼포먼스 카운트용으로 이보다 더 좋은 타이머는 없을 듯 하다. 

    반응형
    반응형

    C 언어에서 파일을 여는 함수 입니다.

    #include <stdio.h>

    FILE *fopen(char *fname, char *mode)

    char *fname : 열고자 하는 파일명

    char *mode : 파일여는 방식

    반환값 : 성공하면 파일 포인터, 실패하면 NULL을 반환한다.

    파일 열기 모드

    "r" : 읽기. 이미 존재하는 파일을 연다.

    "w" : 쓰기. 파일이 없으면 생성하고 있으면 내용을 지운다.

    "a" : 추가. 파일이 없으면 생성하고 있으면 파일의 현재 위치를 끝에 위치시킨다.

    "r+" : 읽기와쓰기. 이미 존재하는 파일을 연다.

    "w+" : 읽기와쓰기. 파일이 없으면 생성하고 있으면 내용을 지운다.

    "a+" : 읽기와쓰기. 파일이 없으면 생성하고 있으면 파일의 현재 위치를 끝에 위치시킨다.

    추가적으로 위의 모드에 "r+b", "rt"등과 같이 "b", "t" 를 추가하여

    "b" : 이진모드, "t" : 텍스트 모드로 열 수 있다.

    사용하지 않으면 기본 값은 텍스트 모드이다.

    ex)

    fp = fopen( url, "rt" );

    반응형

    '프로그래밍(Programming) > c++, 11, 14 , 17, 20' 카테고리의 다른 글

    wchar_t or char .. (유니코드 출력문제)  (0) 2012.10.31
    Timer - getElapsedMilliSecond  (0) 2012.10.31
    함수포인터  (0) 2012.10.31
    GetAsyncKeyState  (0) 2012.10.31
    GetPrivateProfileStringA  (0) 2012.10.31
    반응형

    1. void main()
    2. {
    3. int input;
    4. printf("입력 : ");
    5. scanf("%d", &input);
    6. switch(a)
    7. {
    8. case 1:
    9. printf("1번\n");
    10. break;
    11. case 2:
    12. printf("1번\n");
    13. break;
    14. case 3:
    15. printf("1번\n");
    16. break;
    17. case 4:
    18. printf("1번\n");
    19. break;
    20. }
    21. }


    위와같은 코드는 유지보수에 있어서 문제가 상당히 큽니다.
    case 부분의 실행부분이 커질수록 가독성이 급격히 하락합니다.
    그래서 보통 어느정도 생각이 조금 박힌 사람은 다음과 같은 코드를 사용합니다.

    1. void func_one()
    2. {
    3. printf("1번\n");
    4. }
    5. void func_two()
    6. {
    7. printf("2번\n");
    8. }
    9. void func_three()
    10. {
    11. printf("3번\n");
    12. }
    13. void func_four()
    14. {
    15. printf("4번\n");
    16. }
    17. void main()
    18. {
    19. int input;
    20. printf("입력 : ");
    21. scanf("%d", &input);
    22. switch(a)
    23. {
    24. case 1:
    25. func_one();
    26. break;
    27. case 2:
    28. func_two();
    29. break;
    30. case 3:
    31. func_three();
    32. break;
    33. case 4:
    34. func_four();
    35. break;
    36. }
    37. }


    프로그램의 구조화를 적용해서 그나마 조금 낫게 만들었네요..
    하지만 switch-case 가 남아있어 case 의 몸통이 커질 가능성은 아직 남아있네요..
    저 switch-case 를 어떻게 없앨 수 있는가..
    여기서 사용할 수 있는게 "함수 포인터 배열" 입니다.

    여기서 잠깐
    함수 포인터란?
    C/C++ 컴파일러는 소스코드를 컴파일할 때, 심벌테이블을 만듭니다(여기까진 다 아시져?)
    이 심벌테이블에 들어가는건 변수의 주소 뿐 아니라 함수의 실행코드의 시작위치까지 저장이 되는데요,
    (그 단적인 예로 사용자 정의 함수의 이름과 변수의 이름을 똑같이 쓰면 말도 안되는 동작이 가끔씩 나옵니다.)
    (변수명 - 주소), (함수명-주소) 의 쌍으로 저장을 합니다. 이러한 특징 때문에 함수의 이름을 포인터처럼 사용할 수가 있는것이져..
    말이 너무 길었네요 우선 함수포인터의 선언형태부터 보져

    void(*func_ptr)();
    와 같은 형식으로 선언합니다.
    연산자 우선순위상 다음과 같은 선언을 하게되면
    void *func_ptr();
    void * 를 리턴하는 함수를 선언하는걸로 인식해서 컴파일러는 에러를 뱉어냅니다.
    따라서 () 로 우선순위를 잡아주는거죠...

    간단한 함수포인터 사용 예제입니다.

    1. void (*func_ptr)();
    2. void func_one()
    3. {
    4. printf("1번\n");
    5. }
    6. void main()
    7. {
    8. func_ptr = func_one;
    9. (*func_ptr)(); //func_one() 함수 실행
    10. }


    위에서 말씀드린것 처럼 함수의 이름이 포인터로 사용되어 호출하는 모습입니다.

    이걸 좀더 응용해서 switch-case 를 없애고 가독성을 확보해보도록 하겠습니다.

    1. void (*func_ptr[4])();
    2. void func_one()
    3. {
    4. printf("1번\n");
    5. }
    6. void func_two()
    7. {
    8. printf("2번\n");
    9. }
    10. void func_three()
    11. {
    12. printf("3번\n");
    13. }
    14. void func_four()
    15. {
    16. printf("4번\n");
    17. }
    18. void main()
    19. {
    20. int input;
    21. func_ptr[0] = func_one;
    22. func_ptr[1] = func_two;
    23. func_ptr[2] = func_three;
    24. func_ptr[3] = func_four;
    25. printf("입력 : ");
    26. scanf("%d", &input);
    27. (*func_ptr[input-1])();
    28. }


    switch-case 가 사라져서 아주 깔끔한 형태가 되었네요
    어때요? 참 쉽져? ㅋㅋㅋ

    사족을 달자면, 현재 MFC 에서는 위와 같은 방법으로 메세지를 처리하고 있습니다. 

    반응형
    반응형

    어디선가 퍼온글

    GetAsyncKeyState() 함수를 호출할 경우
    키가 눌려져 있으면 최상위 비트가 1인 short형(2Byte) 리턴값을 리턴합니다
    키가 눌려져 있지않으면 최상위 비트가 1이 아닌 어떠한 값을 리턴합니다
    그래서 0x8000(최상위 비트가 1)과 & 연산을 해주면
    이 키가 눌려져 있는 상태인지 아닌지 알 수 있습니다


    예) if ( GetAsyncKeyState( VK_SHIFT ) & 0x8000 )

    반응형
    반응형

    안녕하세요 단편으로는 처음 쓰는 것 같습니다.

    이 단락으로는 조금 어색할 지도 모르겠지만 윈도우 에서 지원하는 INI 파일의 입출력 입니다.

    그래서 API 의 메뉴에 쓰게 됩니다.

    게임이나 기타 유틸등을 만들때 상당히 파일 입출력에 대해 고민을 하게 됩니다.

    그래서 이번에 이렇게 글을 올리게 됩니다.

    아직 모르시는 분이 있을지? 도 몰라서 ^^;

    여기에서 쓰이는 함수에는 여러가지가 있습니다.

    그 중에서도 가장 많이 쓰이는 Int 형과 String 형에 대해 알아보겠습니다.

    struct 와 Section 이 있지만 Section 은 거의 쓰이지 않는 편 이고 Struct 와 같은 경우는 바이너리로 쓰는게 더 편하기 때문에^^; 생략합니다.

    미리 알아 두어야 할 점은 쓰기에는 STRING 으로 INT 를 그냥 쓸수 있어서 INT 형을 쓰는 함수는 없습니다.

    그리고 INI 파일에 쓰이는 형식이 있습니다.

    [TITLE] //단락의 시작

    NAME = JAEJIN //변수 = 값

    이런 식 으로 되어 있습니다. 단락의 시작으로 부터 그 아래의 값들을 찾는 형식으로 되어 있습니다. 단락은 대 괄호로 나타내구요^^

    첫번째로 쓰기에 쓰이는 함수는

    WritePrivateProfileString 입니다.

    WINBASEAPI
    BOOL
    WINAPI
    WritePrivateProfileStringA(
    IN LPCSTR lpAppName,
    IN LPCSTR lpKeyName,
    IN LPCSTR lpString,
    IN LPCSTR lpFileName
    );

    선언에는 이렇게 되어 잇습니다.

    lpAppName 에는 단락의 시작 의 이름이 들어 갈 것이고,

    lpKeyName 은 변수가 들어 갑니다.

    lpString 은 값이 들어 갈 것이고

    lpFileName 에는 파일의 경로와 이름이 들어갑니다.

    #include <windows.h>


    int _tmain(int argc, _TCHAR* argv[])
    {
    ::WritePrivateProfileString( "TITLE", "STRING_KEY", "NAME", "./TEST.INI" );
    ::WritePrivateProfileString( "TITLE", "NUMBER_KEY", "1024", "./TEST.INI" );
    }

    자 이렇게 쓴다면 INI 파일에는

    [TITLE]
    STRING_KEY=NAME
    NUMBER_KEY=1024

    이렇게 기록이 될 것 입니다.

    눈으로 보이기 때문에 상당히 편안한 점이 있습니다.

    자 그다음은 읽기 차례 입니다. 위에서는 String 으로 썼지만 NUMBER_KEY 는 INT 형 입니다.

    그래서 타입 캐스팅을 하지 않기 위해 INT 형을 읽는 함수가 제공이 됩니다.

    여기서 제공 되는 함수는

    GetPrivateProfileString

    GetPrivateProfileInt

    2가지 입니다.

    WINBASEAPI
    DWORD
    WINAPI
    GetPrivateProfileStringA(
    IN LPCSTR lpAppName,
    IN LPCSTR lpKeyName,
    IN LPCSTR lpDefault,
    OUT LPSTR lpReturnedString,
    IN DWORD nSize,
    IN LPCSTR lpFileName
    );

    일단 String 의 원형은 이렇게 됩니다.

    lpAppName 은 단락의 시작을 쓰시면 됩니다.

    lpKeyName 은 변수가 될 것 이고,

    lpDefault 는 만약 저 변수가 없다면 기본으로 읽을 대체문자열을 나타냅니다.

    lpReturnString 은 값을 받을 버퍼를 나타내고,

    nSize 는 버퍼의 사이즈를 입력 하면 됩니다.

    lpFileName 은 파일을 읽을 경로와 이름을 쓰시면 됩니다.

    WINBASEAPI
    UINT
    WINAPI
    GetPrivateProfileIntA(
    IN LPCSTR lpAppName,
    IN LPCSTR lpKeyName,
    IN INT nDefault,
    IN LPCSTR lpFileName
    );

    Int 의 원형 입니다.

    lpAppName 은 단락의 시작을 쓰시면 되고

    lpKeyName 은 변수입니다.

    nDefault 는 값을 못찾으면 대체로 쓸 숫자를 적고,

    lpFileName 은 파일의 경로와 이름을 쓰시면 됩니다.

    #include <windows.h>


    int _tmain(int argc, _TCHAR* argv[])
    {

    char szBuffer[ 256 ] = {0, };
    ::GetPrivateProfileString( "TITLE", "STRING_KEY", "Unknown", szBuffer, 256, "./TEST.INI" );
    int iResult = ::GetPrivateProfileInt( "TITLE", "NUMBER_KEY", 0, "./TEST.INI" );

    }

    아까 썼던 것을 다시 읽는 소스 입니다.

    아까 TITLE 이란 단락으로 STRING_KEY 에 NAME 이란 값을 입력 했습니다. 만약 이 단락을 찾지 못하면 Unknown 이라는 STRING 이

    szBuffer 에 찰 것이고, 찾았다면 NAME 이라는 값이 Buffer 에 들어옵니다.

    아래 것도 마찬가지로 TITLE 이라는 단락으로 NUMBER_KEY 에 1024 를 썼습니다. 찾지 못하면 0 이 리턴되고 찾았다면 1024가 리턴 될 것 입니다.

    소스도 첨부 하겠습니다.

    이제는 txt 로 노가다 뛰시는 분들은 텍스트 노가다를 줄여 주시고. 텍스트는 그리고 위험하게 버퍼가 어긋나면

    바로 뻑이 납니다.

    그래서 저는 이걸 자주 씁니다^^

    주의 할 점은 마지막 인자에 FileName 을 쓰는데 앞에 ./ 를 꼭 붇여서 CurrentFolder 라고 명시를 하셔야 합니다.

    하지 않으면 못 읽습니다.

    또 유용한 점이 있다면 게임을 만드는 개발자 입니다.

    유저의 해상도를 파일에 기록하여 런처를 만든다고 합시다. 그러면 int 형으로 기본 1024 로 기록을 하였다 한다면,

    유저가 이걸 1280 으로 바꾸었습니다. 하지만 Read 시에 해상도의 단락을 찾지 못하면,

    여기에 있는 Default 로 1024로 마춰 줄 수도 있습니다.

    짧은 내용이고 대부분 많은 분들이 아는 내용이지만. 혹여나 해서 올려봅니다^^


    반응형

    '프로그래밍(Programming) > c++, 11, 14 , 17, 20' 카테고리의 다른 글

    함수포인터  (0) 2012.10.31
    GetAsyncKeyState  (0) 2012.10.31
    forceinline VS inline  (0) 2012.10.31
    보다 더 완성도 있는 프로그램을 위한 assert(0) 함수  (0) 2012.10.31
    enum with namespace  (0) 2012.10.31

    + Recent posts