반응형


일반적인 String compare 함수는 정확한 string을 비교해서 처리해야 하는 경우들이 많습니다.


그런데 간혹, 문자열이 엇비슷하면 같다고 처리하고 싶을 때도 있습니다.


예를 들면,  

1. "나 ^^ 이뽀 :)~" 와 "나 ^^;;; 이뽀~~~???" 이런 문자열들을 같은 취급 하고 싶은 경우, 

2. "이 름" 과 "이름" 과 같이 공백을 무시하고 싶은경우,


3. 마지막으로  "오늘 아침 날씨" 와 "오늘 오전 날씨" 와 같이 의미 갖는 단어들을  같은 것으로  취급하고 싶은 경우,


들 일것입니다.


3번은 단순 코드로는 해결하기 힘든 내용일 것입니다. 

'비슷한 문자열'이라는 개념을 정리하는 차원에서 써 놓은 항목입니다.



일단 순수한 코드차원에서 접근하자면, 1,2번의 경우에 대해서는 간단한 해결책이 있습니다.


특수문자나 공백을 제외하고 문자열을 비교하는 코드를 작성하여 이를 사용하면 됩니다.





#include <string>

using namespace std;



bool inline
is_ignorable_ch(unsigned char c)
{
if (c<= 0x2F) return true; // control code
/*
U+0020      20  SPACE
U+0021  !   21  EXCLAMATION MARK
U+0022  "   22  QUOTATION MARK
U+0023  #   23  NUMBER SIGN
U+0024  $   24  DOLLAR SIGN
U+0025  %   25  PERCENT SIGN
U+0026  &   26  AMPERSAND
U+0027  '   27  APOSTROPHE
U+0028  (   28  LEFT PARENTHESIS
U+0029  )   29  RIGHT PARENTHESIS
U+002A  *   2a  ASTERISK
U+002B  +   2b  PLUS SIGN
U+002C  ,   2c  COMMA
U+002D  -   2d  HYPHEN-MINUS
U+002E  .   2e  FULL STOP
U+002F  /   2f  SOLIDUS
*/  
else if(0x3A <= c && c<= 0x40) return true;
/*  
U+003A : 3a  COLON
U+003B  ;   3b  SEMICOLON
U+003C  <   3c  LESS-THAN SIGN
U+003D  =   3d  EQUALS SIGN
U+003E  >   3e  GREATER-THAN SIGN
U+003F  ?   3f  QUESTION MARK
U+0040  @   40  COMMERCIAL AT
*/

else if(0x5B <= c && c<= 0x60) return true;
/*
U+005B  [   5b  LEFT SQUARE BRACKET
U+005C  \   5c  REVERSE SOLIDUS
U+005D  ]   5d  RIGHT SQUARE BRACKET
U+005E  ^   5e  CIRCUMFLEX ACCENT
U+005F  _   5f  LOW LINE
U+0060  `   60  GRAVE ACCENT
*/
else if(0x7B <= c && c<= 0x7E) return true;
/*
U+007B  {   7b  LEFT CURLY BRACKET
U+007C  |   7c  VERTICAL LINE
U+007D  }   7d  RIGHT CURLY BRACKET
U+007E  ~   7e  TILDE
*/
return false;
}

bool
is_similar(const std::string& str1, const std::string& str2)
{
const char* s1 = str1.c_str();
const char* s2 = str2.c_str();
int l1 = str1.size();
int l2 = str2.size();

int i=0,j=0;
for( ;i<l1 && j<l2;)
{
if(is_ignorable_ch(s1[i])){i++; continue;}
if(is_ignorable_ch(s2[j])){j++; continue;}
if(s1[i] != s2[j]) return false;
i++;
j++;
}

for(i ;i<l1;i++)
if(!is_ignorable_ch(s1[i])) return false;

for(j ;j<l2;j++)
if(!is_ignorable_ch(s2[j])) return false;

return true;
}


#define TEST(P1, P2, _EXPECT) do{\
int r = is_similar(P1, P2);\
printf( "%s\t r= %s:\t %s, %s\n"\
,(r == (_EXPECT))?"PASS":"FALSE" \
,(r==true)?"true":"false"\
,#P1 \
,#P2);\
}while(0)

void compare_test()
{

int no = 0;
TEST("","", true);

TEST("", "aaa", false);


TEST("차차차... 해", "", false);

TEST("안녕하세요. ", " 안 녕 하세요, ",true);
TEST("hello... 나는 누구 ?", "hello나는누구",true);
TEST("hello... ", "he ll. o",true);
}



result 


PASS  r= true:  "", ""

PASS  r= false:  "", "aaa"

PASS  r= false:  "차차차... 해", ""

PASS  r= true:  "안녕하세요. ", " 안 녕 하세요, "

PASS  r= true:  "hello... 나는 누구 ?", "hello나는누구"

PASS  r= true:  "hello... ", "he ll. o"



반응형



Singleton pattern은 객체의 인스턴스가 process내에서 1개만 생성되도록 하는 것입니다.


이렇게 하기 위해서는 global 객체를 하나만 유지하고, 일반적으로 getInstance() 와 같은 class의 스테틱 메소드(static method)를 통해서 생성된 instance를 얻어가는 구조입니다.


주로 XXXX Manager 와 같은 management instance 류 들이 singleton으로 작성됩니다.



가장 simple한 구현체는 아래와 같은 형식입니다.


class Singleton

{

  public:

    static Singleton* getInstance();

  private:

    Singleton()=default;

};


Singleton* 

Singleton::getInstance();

{

    static Singleton inst;

    return &inst;

}

또는 

Singleton* 

Singleton::getInstance();

{

    static Singleton* inst = new Singleton();

    return inst;

}

또는 

static Singleton* __inst = nullptr;

Singleton* 

Singleton::getInstance();

{

    if(__inst == nullptr)

    {

       __inst = new Singleton();

    }

    return __inst;

}


하지만, 이렇게 작성된 코드는 객체 생성 시점에 쓰레드 세이프(thread safe)하지 않기 때문에 , single thread환경에서는 이슈가 없겠지만,

multi-thread환경에서는 이슈가 생길 가능성이 다분합니다.


이를 위해서 각 platform 환경에 맞춰서 thread safe 형식을 추가하여 작업을 해야 하는 상황이 발생하죠.

pthread 를 지원하는 환경이면, pthread_once 와 같은 함수들 말이죠.



C++ 11에서 부터는 언어 차원에서 pthread_once 와 같은 기능을 할 수 있는 기능을 지원하게 되었습니다.

std::call_once() 입니다. (http://www.cplusplus.com/reference/mutex/call_once/?kw=call_once)


이 call_once를 이용하면 platform과 연관성을 줄이면서 Singleton 디자인 패턴(design pattern)을 완성 할 수 있습니다.


[std::call_once를 이용한 Singleton pattern 구현]

#include <new>

#include <memory>

#include <mutex>

#include <stdio.h>


class Singleton

{

public:

    static Singleton& getInstance();


    void log(){

        printf("instance: %p", __instance.get());

    }

    ~Singleton(){

        printf("delete instance : %p", this);

    }

private:

    Singleton() = default;

    

    Singleton(const Singleton &) = delete;

    Singleton &operator=(const Singleton &) = delete;


    static std::unique_ptr<Singleton> __instance;

    static std::once_flag __once;

};



std::unique_ptr<Singleton> Singleton::__instance;

std::once_flag Singleton::__once;


Singleton& 

Singleton::getInstance()

{

    std::call_once(__once, []() {

      __instance.reset(new Singleton);


      printf("create instance : %p\n", __instance.get());


    });


    return *__instance.get();

}


void 

test_std_once()

{

    Singleton& ins= Singleton::getInstance();    

    ins.log();

}








반응형

오른쪽 참조, R-Value Reference 란?

 

 

시기는 잘기억이 안나는데 어느시점에서 부턴가 C++ 쪽에서 rvalue , move sementic 이런 용어들이 자주 거론 되기 시작한 시기가 있었습니다.

꽤 많은 사람들이 여기에 대해서 유익한 개념이라고 언급했던 기억이 나네요.

 

블로그로 정리하기에는 좀 오래된 내용이지만, 한번 머리속을 정리해볼겸해서 작성하게 되었습니다.

 

 

C++11 에서 도입된 새로운 특성중 의미이동(move semantic) 과 오른쪽 참조가 있습니다.

이것은 불필요한 복사를 줄이는 것을 목적으로 합니다.

 

여기에 대해서 알아보기 전에 이미 C++의 특성에 대해서 많이 알고 계신 분도 있겠지만, 그렇지 못한 분들도 있을것이라서 ,

기본이 되는 내용이지만 생성자, 복사 생성자 등에 대해서 언급을 하고 넘어가겠습니다. 

 

 

객체 복사와 복사 생성자

 

객체 복사와 복사 생성자를 설명하기 위해 간단한 예제를 준비 했습니다.

getMyObj()는 MyObj의 지역(local) 객체를 반환합니다.

MyObj getMyObj()
{
  MyObj temp;
  temp.set(10);
  return temp; // temp 라는 지역 객체(local object)를 리턴합니다.
}

//지역(local) 객체는 의 life cycle 스코프(scope)을 벗어나면 삭제됩니다. 따라서 위의 함수에서 return한 temp 의 삭제 시점이 매우 중요합니다.
//아래 main 함수에서는 a라는 객체를 선언하면서 getMyObj(); 를 호출 했습니다.

int main(void)
{
  MyObj a = getMyObj();  //(1) 임시 객체 temp 가 a로 복사되고, temp는 사라짐

  MyObj b = a;     //(2) a를 b에 복사함.
  b.printData();
}

 

MyObj 라는 클래스(class) 형태가 어떤 모양일지는 모르겠지만,

main 함수의 문맥상으로는 크게 문제될 것이 없어보입니다.

getMyObj() 가 로컬 객체를 넘겼다 하더라도, a 로 복사(copy)가 발생 할 것이고 , a는 다시 b로 복사(copy) 될 것으로 보이기 때문입니다.

그렇지만 MyObj가 아래와 같은 class라고 한다면, 기능상 심각한 오류가 발생할 가능성이 있습니다.
class MyObj
{
  int* __data = nullptr;

  public:
    MyObj()    // 일반 생성자
    {
       	__data= new int[3000];
		for (int i = 0 ; i < 3000 ; i++)
	 		__data[i] = 0; 
    }

    virutal ~MyObj()
    {
		delete __data;
    }    

    void set(int val)
    {
		for (int i = 0; i < 3000;i++)
			__data[i] = val ;
    }
    
    void printData(){
        for ( int i =0; i<3000;i++)
               printf("data[%d] = %d", i, __data[i]);
    }

};
 
위의 main 함수에서 getMyObj()를 호출하여 temp 가 삭제 되는 부분을 다시 살펴봅시다.
   MyObj a = getMyObj();  //(1) 임시 객체 temp 가 a로 복사되고, temp는 사라짐

이 과정을 좀 풀어서 얘기하면 다음과 같습니다.

1) getMyObj() 함수는 temp를 return합니다.

2) temp는 a에 복사가 됩니다.

3) temp는 삭제가 되면서 __data 를 삭제(delete)합니다.

4) 여기까지 진행되면, a.__data는 삭제된 메모리 주소를 가리키게 됩니다.(dangling pointer)

이 내용을 좀더 자세히 확인하기 위해서는 복사 생성자를 알아봐야 합니다.
 

복사 생성자 (copy constructor)

 

사용자가 작성한 복사 생성자가 아닌, 컴파일러(compiler) 가 제공하는 기본 복사 생성자는 다음과 같이 동작 하게 됩니다.

 

MyObj( const MyObj& obj)

{

   __data = obj.__data;  //a.__data 는 address를 담고 있는 값이라서, __data에 해당 주소값(address) 만 복사가 됩니다.

  // 복사 생성자가 없는 경우 default 복사는 이것과 동일한 결과를 만듭니다.

}

 

__data라는 포인터(pointer,주소를 담을 수 있는 변수) 값을 복사하게 됩니다.

 

MyObj b=a; 라는 코드를 보면, 

a.__data는 메모리 주소값을 담을 수 있는 변수이기에, a._data 값을 b.__data 로 주소만  복사하지,  __data가 가리키는 곳의 실제 데이타를 복사 하지 않습니다.

결과적으로  a와 b가 모두 같은 주소를 바라보는 __data를 갖게 됩니다.

이 경우  a가 삭제된다면,  b.__data가 가리키고 있는 곳의 data를 메모리가 해제 되었기 때문에, b.__data 는 dangling 된 pointer가 됩니다.

 

때문에 MyObj b=a; 에서는 반드시 복사가 이뤄져야 합니다.

 

이 때문에 복사 생성자는 다음과 같이 작성되어야 합니다.

 

MyObj(const MyObj& obj)  // 복사 생성자

{

__data= new int[3000];

for (int i = 0; i < 3000;i++)

__data[i] = obj.__data[i] ;

}

 

반면에,

MyObj a = getMyObj();
 
에서 data를 모두 copy하는 과정은 temp 객체는 더이상 사용할 일이 없기 때문에 불필요한 작업입니다.
그런데 class MyObj가 들고 있는 data의 내용이 크고 복잡하다고 한다면,  상당히 많은 시간을 소모하게 됩니다.
 

A의 data가 커지면 커질수록, 복잡해지면 복잡해질 수록 복사 비용은 커지게 됩니다.

 

맨 처음 코드의 main() 함수에서 보면, (1)(2)가 미묘하게 다릅니다.

 

(1)은 임시 객체를 a에 복사하는 것이고,  (정확하게는 미묘한 어떤 것이 더 있긴 하지만 설명의 편의를 위해 여기까지만)

(2)는 b에 임시 객체가 아닌 a를 넘기는 것입니다.

 

C++ 개발자들(C++언어를 개선하고자 하는 사람들)은 여기에 착안을 해서 복사와 이동을 분리해서 생각하게 되었습니다.

다시 생각해보자면,

(1) 은 데이타를 넘겨주고 임시 객체는 사라지기 때문에 사실상 이동과 동일합니다..

(복사를 하고 삭제한다.!)

(2) 는 데이타를 넘겨주고 객체가 2개 다 유지가 되기 때문에 복사에 해당합니다.

 

(1)과 같이 이동을 원하는 경우에는 복사 생성자를 통한 처리가 아니라 다른 함수를 제공하게 되면 훨씬 성능에 이득을 취할수 있게 되죠.

 

void MoveFrom(MyObject& from)

{

   __data = from.__data; // data를 copy 하는 것이 아니라 주소만 복사함.

   from.__data = nullptr; // 원본의 data는 nullptr로 만듬. 삭제가 안되도록.

}

 

 

 

a.MoveFrom(getMyObj());  

 

이렇게 사용하면 됩니다.

 

 

이것의 단점은 ,

1. 객체를 생성하는 시점에 처리 할 수 없습니다.

 

2. 넘겨주는 객체가 더이상 사용안되는지 사용되는지 판단을 개발자가 해야 하여 실수의 여지가 있고,

   코드가 변경될때 마다 항상 다시 확인을 해야 합니다.

 

3. 함수 형태로 클래서 설계자가 작성을 해야 하기 때문에, 이름과 사용처라 모두달라 유지 보수가 힘듭니다.

 

 

 

이런 이동에 대한 고민과 경험들은 C++언어의 개선에 도움 되었고, 의미 이동이라는 개념과 R-Value Reference 라는 것이 

C++11에 추가가 되었습니다.

 

 

C++ 개발자들은 이 move semantic 과 R-value reference 에 대해서 사람들은 드디어 획기적인 성능 개선이 이뤄질 수 있겠다는 기대들을 하게 됩니다.

 

실제로 STL 전반에 걸쳐서 의미 이동이 추가되어 상당한 성능 개선이 이루어졌습니다.

 

 

 

자 이제 R-Value Reference 라는 것이 무엇인지, 이것에 의해서 무엇이 달라지는지 보도록 합시다.!!

 

 

오른쪽 참조, R-Value Reference 란?

 

 

오른쪽 참조는 임시 객체와 매우 밀접합니다.

 

다음 식으로 L value 와 R value를 간단히 설명드리겠습니다.

 

A = B  라는 식이 있다고 한다면,

 

A는 L-Value 이고 B는 R-Value 입니다. 즉 왼쪽에 있는 값은 L value, 오른쪽에 있는 값은 R value로 보면 됩니다.

왼쪽 편에 있는 값들은 뭔가 저장할 수 있는 것이어야 합니다.

12 =B  이런식으로 12라는 상수값은 사용될 수 없습니다.

 

오른쪽 값 형식은 상수나 변수 모두 올 수 있습니다.

A = 12

A = B+4

A = 4-3

A = B   // 실질적으로 L-value = L-value 라서 복사 생성자가 실행됨.

 

R-Value를 좀 다르게 표현하면, L value에 assign하기 위한 임시적인 값 이라고 표현하는게 더 올바른 것 같습니다.

 

편의상 임시 객체라고 표현 하겠습니다.

 

정수에 대한 처리를 위한 Integer라는 class가 있다고 합시다.

 

Integer a;   // a는 일반 객체

 

Integer b = a;  // b는 일반 객체,  a는 일반 객체

 

Integer c = 10; // c는 일반 객체, '10'을 담는 임시 객체 생성

 

Integer d = b+c;  d,b,c는 일반객체,  (b+c)는 b와 c를 더한 결과를 담는 임시 객체 생성

 

 

우리가 연산자등을 이용할때 임시 객체들이 의도치 않게 생성되는 경우들을 흔히 볼수 있습니다.

고수 개발자 분들은 코드를 볼때 그런 부분들 까지 염두해 두고 살펴보죠.

 

이때 이런 임시 객체들은 한번 생성되고 바로 버려지는데, 이를 복사하기 위해서 소비되는 비용( 리소스, CPU) 가 너무 아까운 것이죠.

이런 부분들은 바로 성능에 영향을 미치게 되고 말이죠.

 

그리고 이런 부분들을 개발자들에게 맡기기에는 너무나 불안한 요소들이 너무 많아서, 임시 객체를 처리하기 위한 특별한 문법이 추가되었습니다. 

 

R-Value Reference 라고 합니다.  형식은 "&&"  입니다.

&&??? 이게 뭐냐구요?

 

타입명&& 변수 이렇게 사용을 하게 되면, 임시 객체를 가리키는 레퍼런스가 만들어지게 됩니다.

 

void Integer::Get(Integer&& value)  이런 형식이죠.

 

만약  아래와 같이 2개의 함수가 만들어진다고 하면,

void Integer::Get(Integer&& value)   --> 임시 객체가 인자로 들어올때 처리되는 함수

void Integer::Get(const Integer& value)  --> 일반 객체가 인자로 들어올때 처리되는 함수

가 됩니다.

void Integer::Get(Integer&& value) 이 함수가 없다면, 모든 객체에 대해서 void Integer::Get(const Integer& value) 가 동작하게 됩니다.

 

 

 

그럼 처음에 만들었던 MyObj class에 이동 생성자를 추가해봅시다.

 

이동 생성자는 인자로 받는 객체가 임시 객체이기 때문에 더이상 사용 안되고,사라질 것을 전제로 작성되어야 합니다.

 

MyObj(MyObj&& obj)  // 이동 생성자

{

__data = obj.__data; // 주소 복사

obj.__data = nullptr; // 이전 객체에서 pointer는 삭제,

//이동 완료.

}

 

 

입력으로 받은 obj는 바로 사라질 것이기 때문에,  obj의 data를 nullptr로 변환 하는 것은 꼭 필요합니다.

그렇지 않으면,  임시 객체인 obj의  destructor에서 __data를 delete 하여,  현재 객체의 __data는 dangling pointer가 되어버립니다.

 

 

 

class MyObj

{

  int* __data= nullptr;

 

  public:

    MyObj()    // 일반 생성자

    {

       __data= new int[3000];

for (int i = 0 ; i < 3000 ; i++)

__data[i] = 0; 

    }

 

   MyObj(const MyObj& obj)// 복사 생성자

    {

__data= new int[3000];

for (int i = 0; i < 3000;i++)

 __data[i] = obj.__data[i] ;

    }

 

   MyObj(MyObj&& obj)  // 이동 생성자

    {

__data = obj.__data; // 주소 복사

  obj.__data = nullptr; // 이전 객체에서 pointer는 삭제,

  이동 완료.

}

    virutal ~MyObj()

    {

delete __data;

    }    

 

    void set(int val)

    {

for (int i = 0; i < 3000;i++)

 __data[i] = val ;

    }

};

 

MyObj getMyObj()

{

  MyObj temp;

  temp.set(10);

  return std::move(temp);

}

 

int main(void)

{

  MyObj a = getMyObj();  //(1) MyObj(const MyObj&& obj) 이동 생성자가 호출됨

  MyObj b = a;     //(2) MyObj(const MyObj& obj) 복사 생성자가 호출됨.

}

 

 
이렇게 이동 생성자와 복사 생성자를 모두 구현해 놓으면,
프로그램 코드에 따라 경우에 따라서 복사가 되고나, 이동이 됩니다.
 
 
 
 
std::move()
 
std::move()는 의미 이동을 위한 도구중 하나입니다.
 
MyObj b = a; 는 복사 생성을 하게 됩니다.
MyObj b = std::move(a); 라고 하게 되면, b는 이동 생성을 하게 됩니다.
 
이런 식으로 경우에 따라서 복사를 이동으로 변환하여 처리 할 수도 있습니다.
이런 강제 이동의 이면에는 이에 따른 불안 요소를 남기게 됩니다.
즉, b로 강제 이동을 했지만, a는 현재 살아있는 상태(변수이기 때문에)이기 때문에 만약 a를 사용하려 한다면, a.__data 는 nullptr 상태이기 때문에 문제가 생깁니다.
 
즉, std::move() 를 사용할 수 있는 경우는 a를 더이상 사용 안해야 합니다.
 
 
 
 
 
 
 

생각해볼 만한것.!

 

컴파일러나 언어 차원에서 성능 개선을 위해 최적화를 해주는 부분들이 존재 합니다.

그런것에 대해서 적어봤습니다.

 

B = 5

A = 10

A = B

 

A = B 에서 A는 복사가 됩니다.

 

그러나 원리를 따지자면,

원래는 B쪽은 r value가 되어야 합니다. 따라서, 다음과 같은 과정이 되어야 합니다.

1. 임시 객체를 만들고 거기에 B가 copy 된다.   

  temp의 복사 생성자를 통해 B가 temp 로 복사가 된다.

   temp(B)  // temp 는 임시 객체 , B는 인자.

 

2. A에게 임시 객체를 넘긴다.  

    A의 이동 연산자를 통해 temp가 A로 이동한다.

    A = temp(B) 

 

그러나 실제로는 이런 불합리한 점을 제거하기 위해서 예외적으로 A의 copy construct 로 처리 됩니다.

 

L value = L value 의 경우에는 copy라고 보시면 됩니다.

 

 

 

Integer getInt()

{

  return Integer(10);

}

 

Integer B = getInt();

 

이런 코드의 경우에는 B의 이동 생성자가 불려서 임시 객체가 copy 되어야 합니다.

그러나 컴파일러가 이런경우는 이동 생성 없이 B 가 곧 임시 객체가 되도록 만들어 줍니다.

마치,

Integer&& B = Integer(10); 

이런 것 처럼 만들어줍니다.

 

이 경우 만약 아래와 같이 복사보다는 move가 낳지 라고 생각해서 아래와 같이 move를 명시화 시킨다면,

Integer B =  std::move(getInt());

 

B의 move 연산자가 동작하여 오히려 성능에 안좋은 영향을 주게 됩니다.

 

반응형


어떻게 compiler version 에 따라 API 지원 피쳐를 구분해서 제공할까 고민하다가 compiler 를 변경해가면서 직접 값을 정리해봤습니다.

예를 들면,

c++11 부터 지원하기 시작한 매우 유용한 feature들이 있습니다.

1. final, override keyword 

2. class 선언부에서 변수 초기화 하는것


이러다 보니, c++11로 개발하고 c++99 용 모듈에서도 사용할 수 있게 할 수 없을까?

하는 아이디어( 요구사항)이 있었습니다. 사실 개발은 c++11로 하고 싶었거든요.

아니면 code를 다시 다 걷어내거나 내부 로직에서 사용하고 있는 auto, lamda등을 다 걷어내야 하는 판국이라서 말이죠.


그래서 나온 아이디어가 이렇습니다.


1. 개발은 C++11로 한다.

2. 다른 모듈에는 header file 과 so( library)를 제공한다.

3. header file만 c++11과 c++99를 구분한다.

4. 모두 해피하다.!! ㅎㅎ


여기서 3번이 문제였는데요.

먼저 확인할 사항이 몇가지 있었습니다.

1. final, override keyword에 의해서 ABI 즉,  API 호환성이 달라지는가?

2. class 선언부에서 변수 초기화가 빠졌을때 이미 compile 된 so의 동작이 달라지는가?


이 두가지를 테스트 해봤습니다.

결과는 문제가 없더군요.


이렇게 해서 C++11로 개발하고 C++99용 header(? 사실 header file 내부에서 구분해서 처리) 를 제공할 수 있게 되었습니다.


물론, c++11의 lamda 를 interface로 사용한다거, auto 를 return 값이나 parameter로 사용한다거나(이건 c++14던가요?) 하는 부분은  API class들에서는 없애야 했습니다.




이 과정에서 조사했던 유용한 정보 하나 있습니다.

바로 gcc와 llvm 에 대해서  C++11 의 지원 feature 를 확인하기 위해서  predefine 되어있는 값들을 조사 한 것인데요.

아래와 같습니다.


조사해놓고 보니까 결국 , __cplusplus 만  확인하면 끝나는 것이었더라구요. ㅠ_ㅠ


gcc 4.6

__cplusplus int 1

__GNUC__ int 4

__GNUG__ int 4

__GXX_ABI_VERSION int 1002

__GNUC_MINOR__ int 6



gcc 4.9 

__cplusplus int 201103

__GNUC__ int 4

__GNUG__ int 4

__GXX_ABI_VERSION int 1002

__GNUC_MINOR__ int 9



llvm 4.6

__cplusplus int 201103

__GNUC__ int 4

__GNUG__ int 4

__GXX_ABI_VERSION int 1002

__GNUC_MINOR__ int 6

__clang__ int 1

__clang_major__ int 3

__clang_minor__ int 4



llvm 4.9

__cplusplus int 201103

__GNUC__ int 4

__GNUG__ int 4

__GXX_ABI_VERSION int 1002

__GNUC_MINOR__ int 9

__clang__ int 1

__clang_major__ int 3

__clang_minor__ int 6





반응형




GCC 에서 지원하는 C++11 피쳐들을 알아보자.


gcc 4.9 에서는 c++11 피쳐가 지원된다는 것은 알고 있을겁니다.


그런데, gcc 4.6에서도  c++11 이 지원된다고 아셨다면, 아래 표를 참조해서 내가 쓸수 있는 피쳐와 그렇지 못한 피쳐를 확인해야 합니다.


gcc 4.6 부터 c++11 피쳐들이 100% 지원되는 것이 아니기 때문이죠.


대표적으로 유용한 override keyword 는 4.7 부터 지원하고 있죠.



<참고  : https://gcc.gnu.org/projects/cxx-status.html#cxx11>

Language FeatureProposalAvailable in GCC?SD-6 Feature Test
Rvalue referencesN2118GCC 4.3__cpp_rvalue_references >= 200610
    Rvalue references for *thisN2439GCC 4.8.1__cpp_ref_qualifiers >= 200710
Initialization of class objects by rvaluesN1610Yes
Non-static data member initializersN2756GCC 4.7__cpp_nsdmi >= 200809
Variadic templatesN2242GCC 4.3__cpp_variadic_templates >= 200704
    Extending variadic template template parametersN2555GCC 4.4
Initializer listsN2672GCC 4.4__cpp_initializer_lists >= 200806
Static assertionsN1720GCC 4.3__cpp_static_assert >= 200410
auto-typed variablesN1984GCC 4.4
    Multi-declarator autoN1737GCC 4.4
    Removal of auto as a storage-class specifierN2546GCC 4.4
    New function declarator syntaxN2541GCC 4.4
New wording for C++0x lambdasN2927GCC 4.5__cpp_lambdas >= 200907
Declared type of an expressionN2343GCC 4.3__cpp_decltype >= 200707
    decltype and call expressionsN3276GCC 4.8.1
Right angle bracketsN1757GCC 4.3
Default template arguments for function templatesDR226GCC 4.3
Solving the SFINAE problem for expressionsDR339GCC 4.4
Template aliasesN2258GCC 4.7__cpp_alias_templates >= 200704
Extern templatesN1987Yes
Null pointer constantN2431GCC 4.6
Strongly-typed enumsN2347GCC 4.4
Forward declarations for enumsN2764GCC 4.6
Generalized attributesN2761GCC 4.8__cpp_attributes >= 200809;
__has_cpp_attribute(noreturn) >= 200809;
__has_cpp_attribute(carries_dependency) == 0 (not implemented)
Generalized constant expressionsN2235GCC 4.6__cpp_constexpr >= 200704
Alignment supportN2341GCC 4.8
Delegating constructorsN1986GCC 4.7__cpp_delegating_constructors >= 200604
Inheriting constructorsN2540GCC 4.8__cpp_inheriting_constructors >= 200802
Explicit conversion operatorsN2437GCC 4.5
New character typesN2249GCC 4.4__cpp_unicode_characters >= 200704
Unicode string literalsN2442GCC 4.5__cpp_unicode_literals >= 200710
Raw string literalsN2442GCC 4.5__cpp_raw_strings >= 200710
Universal character name literalsN2170GCC 4.5
User-defined literalsN2765GCC 4.7__cpp_user_defined_literals >= 200809
Standard Layout TypesN2342GCC 4.5
Defaulted and deleted functionsN2346GCC 4.4
Extended friend declarationsN1791GCC 4.7
Extending sizeofN2253GCC 4.4
Inline namespacesN2535GCC 4.4
Unrestricted unionsN2544GCC 4.6
Local and unnamed types as template argumentsN2657GCC 4.5
Range-based forN2930GCC 4.6__cpp_range_based_for >= 200907
Explicit virtual overridesN2928 
N3206 
N3272
GCC 4.7
Minimal support for garbage collection and reachability-based leak detectionN2670No
Allowing move constructors to throw [noexcept]N3050GCC 4.6
Defining move special member functionsN3053GCC 4.6
Concurrency
Sequence pointsN2239Yes
Atomic operationsN2427GCC 4.4
Strong Compare and ExchangeN2748GCC 4.5
Bidirectional FencesN2752GCC 4.8
Memory modelN2429GCC 4.8
Data-dependency ordering: atomics and memory modelN2664GCC 4.4
(memory_order_consume)
Propagating exceptionsN2179GCC 4.4
Abandoning a process and at_quick_exitN2440GCC 4.8
Allow atomics use in signal handlersN2547Yes
Thread-local storageN2659GCC 4.8
Dynamic initialization and destruction with concurrencyN2660GCC 4.3
C99 Features in C++11
__func__ predefined identifierN2340GCC 4.3
C99 preprocessorN1653GCC 4.3
long longN1811GCC 4.3
Extended integral typesN1988Yes





C++14 지원은 아래와 버전들에서 지원되고 있습니다.



Language FeatureProposalAvailable in GCC?SD-6 Feature Test
Tweak to certain C++ contextual conversionsN33234.9
Binary literalsN34724.3 (GNU) 
4.9 (N3472)
__cpp_binary_literals >= 201304
Return type deduction for normal functionsN36384.8 (N3386) 
4.9 (N3638)
__cpp_decltype_auto >= 201304
Generalized lambda capture (init-capture)N36484.5 (partial) 
4.9 (N3648)
__cpp_init_captures >= 201304
Generic (polymorphic) lambda expressionsN36494.9__cpp_generic_lambdas >= 201304
Variable templatesN36515__cpp_variable_templates >= 201304
Relaxing requirements on constexpr functionsN36525__cpp_constexpr >= 201304
Member initializers and aggregatesN36535__cpp_aggregate_nsdmi >= 201304
Clarifying memory allocationN3664N/A
Sized deallocationN37785__cpp_sized_deallocation >= 201309
[[deprecated]] attributeN37604.9 (N3797)__has_cpp_attribute(deprecated) >= 201309
Single-quotation-mark as a digit separatorN37814.9 (N3797)__cpp_digit_separator >= 201309


반응형

STL의 map,array의 성능에 차이는 없을까?

key값이 string인 경우와 int인 경우의 차이는 얼마나 될까?

이것을 확인하기 위해서 아래와 같이 간단한 성능 테스트 프로그램을 작성해봤습니다.

 

 

stringMapTC()는 strStr 에 들어있는 string들을 key로 하여 map의 요소에 접근하는 테스트

stringUnorderedMapTC()는 strStr 에 들어있는 string들을 key로 하여 unordered_map의 요소에 접근하는 테스트

intMapTC()는 int 값을 key로 하여 map의 요소에 접근하는 테스트

intArrayTC()는 stl::array로 요소에 접근하는 테스트

cArrayTC()는 stl container가 아닌 srcStr[] 에서 직접 35번 요소에 접근하는 테스트

 
 

테스트 결과 - 10000회 수행 시간 (초)

 

함수  데이터 형식  1  2  3  4
stringMapTC()

std::map
<const std::string, int>
0.0049 0.0052 0.0054 0.0053
stringUnorderedMapTC() std::unordered_map
<const std::string, int>
0.0018 0.0019 0.0019 0.0019
 intMapTC()

std::map
<int, const std::string>
0.0006 0.0008 0.0008 0.0008
 intArrayTC()

std::array
<std::string, 100>
0.0001 0.0001 0.0001 0.0001
 cArrayTC()

char* srcStr[] 0.0001 0.0001 0.0001 0.0001
 

 

stringMapTC의 경우는 key compare를 위해 string 을 비교해야 하기에 가장 오래걸립니다.

stringUnorderedMapTC는 map에 비해 약 2.7배 정도 빠른 성능을 보입니다.

code가 길어서 그렇지 성능을 높이려면 map 쓰는 곳을 모두 unordered_map으로 변경하면 비약적인(?) 성능 향상이 이뤄지겠네요 ^^.

 

intMapTC의 경우는 key값이 int라서 string 을 key로 사용하는 것에 비해 많이 빠릅니다. 그러나 stl::array에 비해서는 느리게 결과가 나왔습니다.

당연히 intArrayTC의 경우는 당연히 map보다는 빠를 수 밖에 없습니다. 전체 서치가 아니라 array의 index (= array[index]) 를 key처럼 사용하기 때문에 key값이 당연히 정렬이 되어있어서 빠를 수 밖에 없습니다.

cArrayTC() 와 비슷한 결과가 나올 수 밖에 없습니다.

 

[source code]

#include <time.h>
#include <map>
#include <unordered_map>
#include <array>
#include <string>
using namespace std;

const char *srcStr[] =
    {
        "hello"
        "bounds",
        "contentBounds",
        "contentOpacity",
        "renderOperation",
        "opacity",
        "showState",
        "anchor",
        "anchorZ",
        "transform",
        "childrenTransform",
        "zPosition",
        "zOrderGroup",
        "clipToParent",
        "clipChildren",
        "projection",
        "surfaceOpaque",
        "name",
        "propertyPropagation",
        "implicitAnimation",
        "detach",
        "attach",
        "bounds.",
        "bounds.position",
        "bounds.size",
        "transform.",
        "transform.rotation.x",
        "transform.rotation.y",
        "transform.rotation.z",
        "transform.scale.x",
        "transform.scale.y",
        "transform.scale.z",
        "transform.translation.x",
        "transform.translation.y",
        "transform.translation.z",
        "transform.rotation.anchor.x",
        "transform.rotation.anchor.y",
        "transform.rotation.anchor.z",
        "transform.scale.anchor.x",
        "transform.scale.anchor.y",
        "transform.scale.anchor.z",
        "transform.rotation.xy",
        "transform.scale.xy",
        "transform.translation.xy",
        "transform.rotation.anchor.xy",
        "transform.scale.anchor.xy",
        "childrenTransform.",
        "childrenTransform.rotation.x",
        "childrenTransform.rotation.y",
        "childrenTransform.rotation.z",
        "childrenTransform.scale.x",
        "childrenTransform.scale.y",
        "childrenTransform.scale.z",
        "childrenTransform.translation.x",
        "childrenTransform.translation.y",
        "childrenTransform.translation.z",
        "childrenTransform.rotation.anchor.x",
        "childrenTransform.rotation.anchor.y",
        "childrenTransform.rotation.anchor.z",
        "childrenTransform.scale.anchor.x",
        "childrenTransform.scale.anchor.y",
        "childrenTransform.scale.anchor.z",
        "childrenTransform.rotation.xy",
        "childrenTransform.scale.xy",
        "childrenTransform.translation.xy",
        "childrenTransform.rotation.anchor.xy",
        "childrenTransform.scale.anchor.xy",
        "__showOpacity",
};

double stringMapTC()
{
    std::map<const std::string, int> strMap;
    int i = 0;
    for (auto str : srcStr)
    {
        strMap.insert({str, i++});
    }

    int v = 0;
    clock_t start = clock();
    for (int j = 0; j < 10000; j++)
    {
        v = strMap["childrenTransform.rotation.anchor.x"];
    }
    clock_t end = clock();
    return (double)(end - start) / CLOCKS_PER_SEC;
}


double stringUnorderedMapTC()
{
    std::unordered_map<std::string, int> strMap;
    int i = 0;
    for (auto str : srcStr)
    {
        strMap.insert({str, i++});
    }

    int v=0;
    clock_t start = clock();
    for (int j = 0; j < 10000; j++)
    {
        v= strMap["childrenTransform.rotation.anchor.x"];
    }
    clock_t end = clock();
    return (double)(end - start) / CLOCKS_PER_SEC;
}

double intMapTC()
{

    std::map<int, const std::string> strMap;
    int i = 0;
    for (auto str : srcStr)
    {
        strMap.insert({i++, str});
    }

    clock_t start = clock();
    std::string v;
    for (int j = 0; j < 10000; j++)
    {
        v= strMap[35];
    }
    clock_t end = clock();
    return (double)(end - start) / CLOCKS_PER_SEC;
}


double intArrayTC()
{

    std::array<std::string, 100> strMap;
    int i = 0;
    for (auto str : srcStr)
    {
        strMap[i++] = str;
    }

    clock_t start = clock();
    std::string v;
    for (int j = 0; j < 10000; j++)
    {
        v = strMap[35];
    }
    clock_t end = clock();

    return (double)(end - start) / CLOCKS_PER_SEC;
}

double cArrayTC()
{

    clock_t start = clock();
    std::string v;
    for (int j = 0; j < 10000; j++)
    {
        v = srcStr[35];
    }
    clock_t end = clock();

    return (double)(end - start) / CLOCKS_PER_SEC;
}

#define PRINT_TC(_FUNC)                     \
    {                                       \
        double res = _FUNC();               \
        printf("%.4f %s()\n", res, #_FUNC); \
    }
int main(void)
{

    PRINT_TC(stringMapTC);
    PRINT_TC(stringUnorderedMapTC);
    PRINT_TC(intMapTC);
    PRINT_TC(intArrayTC);
    PRINT_TC(cArrayTC);
}

 

 

테스트 결과

$ ./main
0.0054 stringMapTC()
0.0021 stringUnorderedMapTC()
0.0009 intMapTC()
0.0001 intArrayTC()
0.0001 cArrayTC()
반응형

 

항목 11 :

 

우리가 class를 설계할때, 의도되지 않은 동작을 막기 위해 클라이언트에서 호출 할 수 없도록 막을 필요가 있습니다.

하지만, 특수 멤버 함수들인 C++이 필요에 따라 자동으로 작성된 멤버 함수들에서 이런 제한이 쉽지 않습니다.

특히 , 복사생성자, 대입 연산자등 몇몇에 대해서 private으로 정의하는 경우들이 있습니다.

 

class A{

public:

   int get() const;

   int set(int d);

private:

   A(const A& rhs); // unused ( or not defined)

   A& operator =(const A&); // unused (or not defined)

};

 

이에 관한 언급을 한 것이 11 항목입니다.

 

 

C++11에서는 이를 위한 더 나은 방법을 제공하는데요. 바로 delete 키워드입니다.

public:

  A(const A& rhs) = delete;

  A& operator =(const A* rhs) = delete;

 

이런 형식입니다. 앞으로 이런 형식을 삭제된 함수라고 표현 하겠습니다.

 

뭐가 더 나아졌지? 라는 의문을 가질 것입니다.

 

첫번째는 A(const A&rhs)를 사용 못하게 하기 위해서 선언했다는 표현을 주석으로 할 필요가 없어졌다는 것입니다.

만약, 주석이 없다면 오해의 소지도 약간 있죠.. 

 

두번째는 흥미로운 내용인데, 삭제된 함수는 private이 아니라 public으로 선언하는 것이 관례입니다.

이유는 , 클라이언트 코드가 멤버함수를 사용할때 먼저 접근성을 봅니다. 그리고 나서 삭제 여부를 확인합니다.

따라서 만약 삭제된 함수가 private이었다면, 먼저 private함수를 사용했다는 에러를 보이게 됩니다.

 

삭제된 함수는 접근 권한이 중요한 것이 아니라 의도적으로 삭제한 것이 더 중요한데도 말이죠.

그래서, 오류의 원인이 더 명확하게 밝혀지도록 public으로 선언하는 것입니다.

 

 

 

삭제된 함수의 두 번째 장점은, 어떤 함수에도 적용할 수 있습니다. 

private으로 제한 하는 방법은 class의 멤버 함수에만 사용할 수 있습니다.

 

예를 들어,

bool isLucky(int num); 라는 함수가 있다고 하면,

 

if( isLucky('a'))...

if( isLucky(true)) ...

if( isLucky(3.5)) ...

 

"의도된 코드인가" 라는 질문에는 '아니요' 가 정답이죠. 그러나 'a' 나 true 나 3.5나 모두 빌드되고 실행도 됩니다.

 

이를 엄격히 제한하고자 한다면,

bool isLucky(char) = delete;

bool isLucky(bool) = delete;

bool isLucky(double) = delete;  // double 과 float 을 배제

 

이렇게 되면, 'a' true 3.5f 모두 오류가 발생합니다.

 

 

삭제된 함수의 세 번째 장점은 

클래스 안의 함수 템플릿의 일부 인스턴스화를 방지하려는 목적으로 private을 이용하는 것은 불가능합니다.

아래 코드를 살펴보면 이해가 쉽습니다.

class Widget{

public:

 ...

  template<typaname T> 

  void processPointer(T* ptr)

   {...}

private:

   template<>

   void processPointer<void>(void*); // 오류가 발생함.

};

이 코드가 불가능한 이유는 템플릿 특수화는 반드시 클래스 범위가 아니라 이름공간(namespace) 범위에서 작성해야 한다는 것입니다.

따라서 다음과 같이 삭제된 함수를 사용해야 합니다.

 

class Widget{

public:

 ...

  template<typaname T> 

  void processPointer(T* ptr)

   {...}

};

 

template<>

void Widget::processPointer<void>(void*) = delete;

 

 

 

 

자신이 작성했던 코드를 delete로 바꾸고 컴파일 해봅시다.!!! ㅎ

 

 

[참조] effective modern c++ :항목 11

반응형

항목 9 : typedef 보다는 별칭(using) 을 선호하라.

 

 

STL 컨테이너들을 사용하는 것이 바람직하다. 이건 아마 c++을 사용하는 사람들은 동의 하리라 생각합니다.

std::unique_ptr의 사용 역시 바람직하다는 점에 대해서도 동의 할것 같구요.(저도 동의합니다.)

 

하지만, "std::unique_ptr<unordered_map<std::string, std::string>>" 을 여러번 타이핑하는 것은 하고 싶지 않죠( 120% 공감)

 

이럴때 typedef 를 사용합니다.

typedef std::unique_ptr<unordered_map<std::string, std::string>> UPtrMapSS;

 

C++98 에서 사용하던 방식이죠.

 

C++11에서는 별칭선언(alias declaration) 을 제공합니다.

 

using UPtrMapSSstd::unique_ptr<unordered_map<std::string, std::string>>;

 

 

이 둘의 차이가 있을까요?

 

그전에 한가지!!! (사실 이것 하나만 보더라도 흥미롭습니다.)

 

typedef void(*FP)(int , const std::string&);

 

using FP = void (*)(int, const std::string&);

 

둘 모두 같은 의미입니다. 함수 포인터를 type으로 선언한것인데요.

눈으로 보기에는 using으로 선언한 것이 더 편하고 명확해 보입니다.(물론 함수포인터 선언에 한해서만 봤을때죠.)

 

 

본격적으로 using을 선호해야 하는 이유에 대해서 다룹니다.

 

첫번째 typedef는 template 화 할 수 없습니다. using은 template화 할수 있습니다.

 

template<typename T>

using MyAllocList = std::list<T, MyAlloc<T>>;

MyAllocList<Widget> wg;

 

 

 

typedef를 사용할 경우,

 

template<typename T>

struct MyAllocList {

  typename std::list<T, MyAlloc<T>> type;

};

 

MyAllocList<Widget>::type wg;

 

 

이를 활용하는 template class를 만든다고 합시다.

 

template <typename T>

class Widget {

 private:

   typename MyAllocList<T>::type list;

};

 

C++의 규칙중에는 의존적인 형식(dependent type)의 이름 앞에 반드시 typename을 붙여야 합니다.

 

MyAllocList<T>::type은 템플릿 형식 매개변수 T에 의존적인 형식입니다.

따라서 typename을 붙여야 하며, 또 ::type역시 꼭 필요합니다.

 

보다시피 typedef를 사용하였을때 만들어야 하는 코드를 보면 "어떻게든 되게 만드는" 코드 처럼 보입니다.

 

using은 이런 점들을 보완해서 template을 좀더 자연스럽게 사용할 수 있도록 제공합니다.

template<typename T>

using MyAllocList = std::list<T, MyAlloc<T>>;

template <typename T>

class Widget{

 private:

  MyAllocList<T> list;

}

 

그리고 다음 사항까지도 고려해야합니다.

이 경우 MyAllocList<T>::type은 데이타 타입이 아니라 변수 type이 됩니다.

따라서 반드시 typename을 사용해야 합니다.

 

 

더보기

<검토 필요>

class Wine {...};

 

template <>

class MyAllocList<Wine> {

private:

  enum class WineType {Red, White, Rose};

  WinType type;

};

 

이 경우에  Wine 으로 생성하게 되면 WinType 의 type을 지칭하는 것이 된다.

 

템플릿 메타 프로그래밍은 결국 현재까지 개발된 코드에서 문제 없는가도 중요하지만,

앞으로 개발될 새로운 코드에서도 적용 가능한가도 매우 중요하죠..

 
그때문에 이런 내용들까지도 고려를 해야합니다.

 

 

 

여기서 잠깐, 또 한가지 주의점이 있습니다.

C++11의 STL 에 type과 관련된 template method들이 있습니다.

std::remove_const<T>::type   // const T를 T로 변환

std::remove_reference<T>::type // T& T&&에서 &를 제거

std::add_lvalue_ref<T>::type //T를 T&로 변환

 

이 method들의 구현이 typedef로 구현되어 있기 때문에, ::type이라는 접미사를 붙여야 합니다.

 

이로 인해서 type이라는 이름을 template에서 사용할때는 유의해야합니다.

 

C++14에서는 이를 보완한 멋진 template method들이 있습니다.

 

std::remove_const<T>::type   //C++11

std::remove_const_t<T>      //C++14 에 추가

std::remove_reference<T>::type //C++11

std::remove_reference_t<T>     //C++14에 추가

std::add_lvalue_ref<T>::type //C++11

std::add_lvalue_ref_t<T>     //C++14에 추가

 

아, 그럼 C++11 사용자는 못쓰는건가????

책에 나온 내용을 보면, C++11 컴파일러를 사용하는 사람도 걱정을 할 필요가 없겠더군요.

 

지금까지 살펴본 using을 이용하면 아주 쉽게 처리할 수 있습니다.

 

template <class T>

using remove_const_tstd::remove_const<T>::type;

 

template <class T>

using remove_reference_t = std::remove_reference<T>::type;

 

template <class T>

using add_lvalue_ref_t = std::add_lvalue_ref<T>::type;

 

 

 

[참고] effective modern c++ : 항목 9

 

+ Recent posts