반응형


http://blog.naver.com/sandal0394/220495321210


msdn final 링크

https://msdn.microsoft.com/ko-kr/library/jj678985.aspx


msdn sealed 링크


final 키워드는 C++ 11부터 지원되는 키워드로

final 키워드를 가상함수에 사용할 경우, 파생클래스에서 오버라이딩 할 수 없습니다.

final 키워드를 클래스에 사용할 경우, 파생클래스를 정의할 수 없습니다.


sealed 키워드는 C++ 11의 표준 이전에 사용되던 ms에서 비표준 확장입니다.




https://msdn.microsoft.com/ko-kr/library/0w2w91tf.aspx


sealed는 가상 멤버를 재정의할 수 없거나 형식을 기본 형식으로 사용할 수 없음을 나타내는 ref 클래스에 대한 상황에 맞는 키워드입니다.


ISO C++11 표준 언어에는 Visual Studio에서 지원되는 final 키워드가 있습니다.  표준 클래스에서는 final을 사용하고 ref 클래스에서는 sealed를 사용합니다.  

반응형
반응형

[C++11] default 키워드와 delete 키워드


http://jsfumato.tistory.com/10


* default 키워드

C++11에서부터는 default 키워드를 이용하여 명시적으로 default 생성자를 선언할 수 있습니다. 

C++11 이전부터 C++ 클래스는 내부를 작성하지 않아도 

기본적으로 기본생성자, 복사생성자, 대입연산자, 소멸자 네 가지 멤버함수를 생성합니다. 

**C++11부터는 기본 함수에 move 생성자와 move 대입 연산자가 추가되었습니다. 


모두가 알다시피, 위의 기본 함수들 중 생성자와 소멸자에 대해서는 중요한 규칙이 있습니다. 

어떤 종류의 생성자건, 정의된 생성자가 존재한다면, 컴파일러가 기본 생성자를 만들어주지 않는다는 것입니다. 

위와 같은 경우에 default 키워드를 사용하여 명시적으로 생성자를 만들 것을 요구할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
//아래의 클래스는 복사 생성자를 private에 선언했습니다.
//이 경우 다른 기본 클래스는 생성되지 않기에 아래의 main 문은 컴파일 에러를 발생시키게 됩니다.
class MyClass
{
private:
    MyClass(const MyClass&);
};
  
int main()
{
    MyClass nc;
}


위의 경우를 해결하기 위해서는 따로 기본 클래스를 public에 선언 및 정의해주어야 합니다.

이를 더 간단화시키고 명시적으로 보이기 위해서 사용하는 것이 default 키워드입니다.

default 키워드를 사용하면 기본 생성자 또는 복사 생성자의 기본 값을 정의할 필요없이

손쉽게 default 생성자를 만들 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//default 키워드를 사용하여 명시적으로 기본 생성자를 생성했습니다
//물론 이 뿐만 아니라 명시적으로 복사생성자 또는 대입 연산자도 생성할 수 있습니다.
//이 경우 말 그대로 default한 정의를 하기 때문에 깊은 복사는 이루어지지 않습니다.
class MyClass
{
public:
    MyClass() = default;
// 위의 경우 MyClass(){}와 같습니다
private:
    MyClass(const MyClass& class) = default;
// 따로 복사생성자의 내부를 정의할 필요가 없습니다.
};
  
int main()
{
    MyClass nc;
}


* delete 키워드

delete 키워드도 이와 유사한 방법으로 사용할 수 있습니다. 

대표적으로 복사 및 대입이 불가능한 클래스를 만들고 싶을 때를 예로 들 수 있습니다. 


이전까지는 private:에 복사 생성자 및 대입 연산자를 선언하고 정의하지 않는 방식을 이용하여 

외부에서 접근하는 경우 접근 에러, 실수로 public:에 선언한 경우 링크 에러를 유발하는 방식을 사용했다면, 

C++11부터는 delete 키워드를 사용하여 명시적으로 특정 함수에 대한 정의를 금지할 수 있습니다. 


아래의 예시 코드를 살펴봅시다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyClass
{
public:
    MyClass() {}
    ~MyClass() {}
  
// 아래의 생성자 및 연산자가 생성되지 않도록 명시합니다.
// private에 선언하는 방식보다 더 의도가 명확해집니다.
    MyClass(const MyClass& class) = delete;
    MyClass& operator = (const MyClass& class) = delete;
private:
// 아래의 방식이 기존에 사용되던 방식입니다.
// private에 선언하여 외부에서의 접근을 막고
// 정의부를 제외하여 실수로 public에 위치시키는 경우에도 링크 에러를 유발합니다.
// MyClass(const MyClass& class);
// MyClass& operator = (const MyClass& class);
};


delete는 생성자나 연산자 뿐만 아니라 일반 멤버 함수에도 사용할 수 있기에 

이를 응용하여 암시적 형변환이 일어나는 것을 막을 수도 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
class NoInteger
{
// 아래와 같이 함수를 작성하는 경우 매개변수를 int로 암시적 형변환하여 실행하는 것을 막게됩니다.
// 따라서 foo(13)을 실행하는 경우 에러가 발생합니다.
    void foo(double dparam);
    void foo(int iparam) = delete;
};
  
struct OnlyDouble
{
// 아래의 경우는 double외에는 모든 타입에 대해서 막게됩니다.
    void foo(double d);
    template<typename T> void foo(T) = delete;
};


반응형
반응형


1. 커널 모드와 유저 모드

-메모리 영역은 사용자에 의해서 할당되는 메모리 공간인 유저 영역과 운영체제라는 하나의 소프트웨어를 실행시키기 위해서 필요한 메모리 공간인 커널 영역으로 나뉜다.

-사용자가 사용하는 메모리 영역은 유저 영역이지만 C언어는 메모리 참조가 용이하기 때문에 안정성 제공 측면에서 커널 모드와 유저 모드가 사용된다.

-기본적으로 유저 모드로 동작하다가 Windows 커널이 실행되어야 하는 경우에 커널 모드로의 전환이 일어난다.

-커널 모드와 유저 모드의 차이는 유저 모드에서 동작할 때 커널 영역으로의 접근이 금지된다. 커널 모드일 때는 모든 영역의 접근이 허용된다.

-Windows 에서 운영체제 차원에서 제공되는 시스템 함수들 중 일부는 호출시 커널 모드로 동작한다.

-주의할 점은 모드의 전환은 시스템에 부담을 준다.

-커널 모드와 유저 모드를 제공하는 대상은 Windows운영체제가 아닌 프로세서(Process)이다. 즉 메모리 보호 기능이 CPU에 달려있다.


http://blog.naver.com/cmw1728/220475950348


리눅스 시스템콜

리눅스는 유저 모드와 커널 모드 두개의 실행영역을 가진다. 유저 모드는 일반 응용 프로그램이 실행되는 영역이고, 커널 모드는 커널(:12) 명령이 실행되는 영역이다. 하드웨어에 대한 모든 제어는 커널 모드에서 이루어진다. 두개의 모드로 나눈 이유는 다중 사용자 / 다중 프로세스 운영체제에서 자원에 대한 관리와 보안 을 위함이다.

응용 프로그램은 커널 모드에 진입을 할 수 없으므로 하드웨어에 대한 접근을 할 수 없다. 이는 메모리 할당, 파일 읽기/쓰기와 같은 주요한 작업을 할 수 없음을 의미한다. 하지만 이래서는 제대로 된 응용 프로그램을 만들 수 없 을 것이다.

리눅스 커널은 "간접 접근 방식"으로 이 문제를 해결 한다. 직접 하드웨어에 접근하는 대신 커널에게 자원의 사용을 요청을 하고, 커널이 자원의 사용을 허락하는 방식이다.

이때 커널이 응용 프로그램에 제공하는 함수가 시스템콜이다.


http://www.joinc.co.kr/w/man/12/%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%BD%9C







커널모드란??


커널모드를 검색하다가 이해가 안되던 도중 pdf 파일을 하나 발견하고 내용이 쉽게 이해되 바로 설명하며 포스팅을 써보았습니다.


일단 커널모드란?


리눅스 커널은 프로그램을 실행할 때 크게 두 가지의 모드에서 실행을 한다. 커널 모드 (kernel mode)와 사용자 모드(user mode)가 그것인데, 이 두 모드에 대해서 간단히 설명 을 하자면 커널 모드에서의 프로그램들은 직접적인 하드웨어 요청이나 중요한 시스템 요 청을 할 때 사용되는 모드이다. 일반적으로 사용자가 직접적으로 하드웨어 장치를 사용한 다면 큰 문제가 발생할 수도 있는 데 이것을 방지하기 위해서 커널 모드로 프로그램이 실 행되면서 하드웨어 제어를 커널에서만 할 수 있도록 한 것이다. 원래 사용자가 어느 한 프로그램을 실행하면 사용자 모드에서 실행이 되다가 그 프로그 램이 하드웨어 장치를 사용해야 할 경우 사용자 모드에서 실행되고 있던 프로그램이 커널 모드로 제어권이 넘어가서 하드웨어 장치를 사용하게 된다. 하드웨어 장치를 다 사용하게 되면 다시 프로그램은 사용자 모드로 다시 제어권이 넘어가서 나머지 프로그램을 실행하 게 된다.


설명이 쉽게 나와있는데 이걸로 이해가 안된다면

아래를 보자

*혼자 이해한거라 틀릴 수도 있어요.


1.일반 사용자가 프로그램을 사용한다.



2.일반 사용자는 하드웨어를 직접 제어하게되면 문제가 발생할수 있기에 커널 모드로 프로그램이 실행되면서 하드웨어 제어를 커널에서만 할 수 있도록 하였다.



3.프로그램이 하드웨어 사용이 다 끝나면 다시 그 프로그램은 사용자 모드로 제어권이 넘어가게 된다.


대충 이해 됬으면 개념도를 보도록 하자




이해가 다 되었어도 개념도를 보니 다시 멍해지는 기분이다...

천천히 한번 보도록하자

자료사진과 보고 참고한 곳은 : http://www.scitech.co.kr/upload/book_image/s_402/ESDP-Ch06.PDF

이걸로 많이 참고했습니다.


포스팅은 여기까지.

http://blog.naver.com/stillstick/220463132509



반응형
반응형


예전에 한번 비슷한 글을 레퍼런스 한적이 있는데 같은건지는 모르겠으나.. 괜찮은 글이라서 ㄱ ㄱ


씹어먹는 C++ 토막글 ② - 람다(lambda) 함수



출처 : http://itguru.tistory.com/196



안녕하세요? 이 글은 지난번에 우측값 레퍼런스에 관련한 글에 이어서 두 번째로 쓰는 C++ 토막글 입니다. C++ 토막글에서는 주로 C++ 11 에 추가된 최신 기술들을 다루고 있는데요, 아직 국내에 자료가 많이 부족하다 보니 체계적으로 쓰인 외국 자료들을 번역하는 형태로 제공하고 있습니다. 이 글은 http://ciere.com/cppnow12/lambda.pdf 에 올라온 pdf 자료를 바탕으로 번역된 글입니다. 사실 이 pdf 는 내용은 없고 소스만 있는 발표 자료이지만, 제가 발표자가 되었다고 가정해서 내용을 소개해보고자 합니다. 이 글이 C++ 11 의 강력한 기술인 Lambda 를 이해하는데 많은 도움이 되기를 바라겠습니다 :) 



  서론
 

어떤 벡터의 원소들의 모든 곱을 계산하는 코드를 구성한다고 생각해봅시다. 아마 가장 초보적으로 이 코드를 구성하는 방법은 아마 아래와 같을 것입니다. 


vector<int>::const_iterator iter = cardinal.begin();
vector<int>::const_iterator iter_end = cardinal.end();
int total_elements = 1;
while( iter != iter_end )
{
total_elements *= *iter;
++iter;
}

위는 반복자(iterator) 를 이용해서 cardinal 이라는 vector<int> 의 각 원소들을 순차적으로 참조해가며 total_elements 에 곱해나가는 코드입니다. 아주 직관적이고 단순하지만, C++ 을 처음 배우는, 아직 C++ 의 기능을 전부 접해보지 못한 초보자 수준의 코드이겠죠? 

만일 "나는 C++ 쫌 해" 정도 되는 사람이라는 Functor 를 이용해서 아래와 같은 코드를 짜냈을 것입니다.

int total_elements = 1;
for_each( cardinal.begin(), cardinal.end(), product<int>(total_elements) );
template <typename T>
struct product
{
product( T & storage ) : value(storage) {}
template< typename V>
void operator()( V & v )
{
value *= v;
}
T & value;
};

위 코드는 C++ 고수 답게 for_each 와 Functor 를 이용한 코드를 짜냈습니다. for_each 를 사용해서 이전 코드의 while 문 부분을 싸그리 없앨 수 있지만, 이를 위해 필요한 Functor 를 구성하는 코드가 훨씬 깁니다. 마치 배보다 배꼽이 더 큰 격이군요. 물론 전체적인 코드의 질이 높아졌다고 볼 수 있지만, Functor 을 이용하기 위해 product 라는 구조체를 생성하면서 구질구질하게 생성자도 만들고, 또 void operator() 도 정의해주어야겠죠. 상당히 귀찮은 일이 아닐 수 없습니다.

int total_elements = 1;
for_each( cardinal.begin(), cardinal.end(), [&total_elements](int i){total_elements *= i;} );

자. 그럼 위 코드를 한번 봅시다. 짧고 간결하며, 무엇 보다도 while 문이나 functor 와 같은 구질구질한 코드 없이 깔끔하게 for_each 의 특징을 그대로 살려주었다고 볼 수 있습니다. 즉 Functor 에 들어갈 내용을 product 라는 구조체를 정의하면서 쭉 써내려갈 내용을 한 번에 깔끔하게 정리해놓은 것이지요. 이것이 바로 Lambda 의 위력입니다.

간단히 Functor 를 이용한 코드와 Lambda 를 이용한 코드를 비교해 보아도 그 차이를 실감할 수 있을 것입니다.


// Functor 사용

struct mod

{

mod(int m) : modulus(m) {}

int operator()(int v){ return v % modulus; }

int modulus;

};

int my_mod = 8;

transform( in.begin(), in.end(), out.begin(),

mod(my_mod) );


// Lambda 사용

int my_mod = 8;

transform( in.begin(), in.end(), out.begin(),

[my_mod](int v) ->int

{ return v % my_mod; } );



 

 람다(Lambda) 의 구성

 


자 그럼 Lambda 를 사용하기 위해 Lambda 를 어떻게 C++ 에서 정의하는지 살펴보도록 합시다.

람다는 위 그림과 같이 4 개의 부분으로 구성되어 있습니다. 그 4 개의 부분은 각각 개시자 (introducer), 인자(parameters), 반환 타입 (return type), 그리고 함수의 몸통 (statement) 라 합니다. 일단, 람다 맨 처음에 나타나는 [] 는 개시자로, 그 안에 어떤 외부 변수를 써 넣는다면 람다 함수가 이를 Capture 해서, 이 변수를 람다 내부에서 이용할 수 있게 됩니다 (이에 대한 이야기는 뒤에서...) 위 경우 my_mod 라는 변수를 람다 내부에서 이용할 수 있게 됩니다. 


그 다음의 () 는 람다가 실행시 받을 인자들을 써 넣습니다. 위 람다는 int 형의 v_ 를 인자로 받는 군요. 여기는 그냥 실제로 함수에서 사용하는 인자 리스트와 동일하게 적어주면 됩니다. 이제, 그 옆으로 보면 -> 가 있고 반환 타입을 적어주시면 됩니다. 위 람다의 경우 int 를 리턴합니다. 마지막으로 람다 내부에서 실행할 내용을 적어주면 되는데, 위 람다의 경우 v_ 와 my_mod 를 모듈러 연산해서 그 결과를 리턴하네요. 


만일 우리가


[my_mod](int v_)->int{return v_ % my_mod;}


위와 같이 코드 상에 Lambda 를 썼다고 해봅시다. 그러면 런타임시 이름은 없지만, 메모리 상에 임시적으로 존재하는 클로져 (Closure) 객체가 생성됩니다. 이 클로져 객체는 함수 객체(function object)처럼 행동합니다. (이러한 연유로 람다를 람다 함수라고 부르는 경우가 있습니다 - 사실 엄밀히 말하면 클로져 객체지 함수는 아닙니다)


그렇다면


[](){ cout << "foo" << endl; }()


를 실행하였을 때 어떠한 결과가 나올까요? 일단 


[](){ cout << "foo" << endl; }


로 임시적인 클로져 객체가 생성되었는데 () 를 붙여서 바로 이 임시 클로져 객체를 실행시켜 버리지요. 위 람다는 Capture 하는 변수들도 없고, 인자로 받는 것도 없고 리턴 타입도 없고 (참고로 리턴 타입이 void 일 경우 -> 를 생각 가능합니다) 함수 몸통만 덜렁 있기에 특별히 생각할 것도 없이 함수 몸통만 덜렁 실행되서




라고 나오게 됩니다.


그러면 조금 더 복잡한 예제를 살펴볼까요?


[](int v){cout << v << "*6=" << v*6 << endl;} (7);


는 어떨까요. 


  [](int v){cout << v << "*6=" << v*6 << endl;}


부분에서 인자로 v 를 받는 클로져가 생성되었는데, (7) 로 이 클로져에 인자로 7 을 전달시키면서 실행시켜버립니다. 따라서 모두가 예상 하였던 결과인



가 나오겠네요.


람다 자체가 함수 처럼 자유롭게 사용할 수 있는 것이기 때문에 인자로 (당연히) 레퍼런스 들도 전달 가능합니다. 예를 들어


int i = 7;

[](int & v){ v*= 6; } (i);

cout << "the correct value is: " << i << endl;


를 실행해보면



가 나옵니다.


참고로 받는 인자가 없을 경우, 예컨대 


[](){ cout << "foo" << endl; } 


의 경우 인자 () 를 생략 할 수 있습니다. 즉, 


[]{ cout << "foo" << endl; }


도 동일한 의미입니다. (하지만 [] 는 지울 수 없습니다! ) 


  Capture 
 


사실 많은 경우 우리의 람다 안에서 람다 밖에 있는 변수들에게 접근하고 싶을 때가 있을 것입니다. 물론 "그렇다면 그 변수들을 그냥 인자로 받아버리면 되자나!" 라고 반문할 수 도 있겠지만, for_each 나 fill, transform 등의 C++ 의 파워풀한 STL 을 수행하기 위해서는 인자들을 맞추어 주어야 하는데 이 때문에 함수 내부로 전하고 싶어도 전달하지 못하는 인자들이 있기 마련 입니다. 


따라서 이를 방지하기 위해, 람다 내부와 소통할 수 있는 또다른 문, Capture 를 제공하고 있습니다. Capture 하고자 하는 내용은 앞에서 말했듯이 [] 안에 들어오게 되는데, 대표적으로 아래의 4 개의 형태들이 있습니다.


  1. [&]() { . . . } 외부의 모든 변수들을 레퍼런스로 가져온다. (함수의 Call - by - reference 를 생각)
  2. [=]() { . . . } 외부의 모든 변수들을 값으로 가져온다. (함수의 Call - by - value 를 생각)
  3. [=, &x, &y] { . . . }, [&, x, y] { . . . } 외부의 모든 변수들을 값/레퍼런스로 가져오되, x 와 y 만 레퍼런스/값으로 가져온다
  4. [x, &y, &z] { . . . } 지정한 변수들을 지정한 바에 따라 가져온다. 

그렇다면 한 번 예제를 살펴볼까요.

int total_elements = 1;
vector<int> cardinal;

cardinal.push_back(1);
cardinal.push_back(2);
cardinal.push_back(4);
cardinal.push_back(8);

for_each( cardinal.begin(), cardinal.end(),[&](int i){ total_elements*= i; } );

cout << "total elements : " << total_elements << endl;

위 코드에서 cardinal 에는 1, 2, 4, 8 이라는 원소들이 들어있고 그것을 for_each 를 통해 순회하면서 total_elements 에 곱하게 됩니다. 이 때 Capture 는 & 로 이므로 total_elements 를 Capture 할 수 있고, 람다 외부 변수인 total_elements 를 성공적으로 바꿀 수 있었던 것이죠. 위 코드를 실행하면 예상하던대로



가 나오게 됩니다.

이번에는 조금 더 복잡한 예제를 살펴보도록 합시다. 

template< typename T >
void fill( vector<int> & v, T done )
{
int i = 0;
while( !done() )
{
v.push_back( i++ );
}
}

vector<int> stuff;
fill( stuff,
[&]()->bool{ return stuff.size() >= 8; } );

for_each (stuff.begin(), stuff.end(), [](int i) {cout << i << " " ;});

참고로 클로져 객체는 분명 특정 타입의 객체 이므로 위와 같이 template 에서 typename T 로 받을 수 있습니다. 위의 fill 함수는 특정 타입 T 의 변수 done 으로 클로져 객체를 받았습니다. 이 때, 클로져 객체 자체는 이미 stuff 를 Capture 해서 stuff 에 대한 레퍼런스를 계속 가지고 있는 상태이고, fill 의 while 문에서 돌면서 stuff 의 크기가 8 이하 일 때 까지 수행되게 됩니다. 따라서


로 출력됩니다.



void fill( vector<int> & v, T done )
{
int i = 0;
while( !done() )
{
v.push_back( i++ );
}
}

vector<int> stuff;

fill( stuff,
[&]()->bool{ int sum=0;
for_each( stuff.begin(), stuff.end(),
[&](int i){ sum += i; } );
return sum >= 10;
}
);
for_each (stuff.begin(), stuff.end(), [](int i) {cout << i << " " ;});

머리를 쫌만 굴려보면, 현재 stuff 의 원소 합이 10 이하일 때 까지 stuff 의 원소를 추가하는 람다라고 볼 수 있습니다.
당연히 그 결과는



 한 가지 흥미로운 점은 Capture 를 레퍼런스가 아닌 값으로 할 때 언제 Capture 가 되냐는 것입니다.


int v = 42;

auto func = [=]{ cout << v << endl; };

v = 8;

func();


과연 위 소스에서 v 는 func 이 처음 정의될 때, 즉 클로져 객체가 생성될 때 Capture 될까요, 아니면 func 이 실행될 때 일까요? 만일 전자라면 42 가 출력될 것이고 후자라면 8 이 출력될 것입니다. 


과연!

 

흥미롭게도 람다는 클로져 객체가 처음 생성될때 변수들의 값을 Capture 합니다.


Capture 를 값으로 할 때 주의점은 그 변수들에는 자동으로 const 속성이 붙는 다는 것입니다. 즉 값으로 Capture 시 그 변수들의 내용을 바꿀 수 없습니다. 따라서 아래와 같은 코드는


int i = 10;

auto two_i = [=]()->int{ i*= 2; return i; };

cout << "2i:" << two_i() << " i:" << i << endl;


컴파일 오류 " 'i': a by-value capture cannot be modified in a non-mutable lambda " 가 나게 됩니다. 위 코드에서 i 는 분명히 값으로 받았으므로 const 인데, i *= 2 를 통해 i 의 값을 바꾸려 하고 있으니 오류가 발생한 것입니다. 하지만, 함수 내부에서 i 의 값을 바꾸고자 하면 어떨까요? (물론 실제 외부의 i 의 값은 바뀌지 않습니다... 함수 내부에서만 - 마치 지역 변수처럼 말이죠) 


답은 간단합니다. 람다에 mutable 속성을 추가해주면 됩니다.


int i = 10;

auto two_i = [=]() mutable ->int{ i*= 2; return i; };

cout << "2i:" << two_i() << " i:" << i << endl;


이로써 람다 내부에서 i 의 값을 변경할 수 있습니다. 물론, 다시 말하지만 외부의 i 의 값이 바뀌는 것이 아닙니다. 오직 람다 함수 내에서  '어떤 다른 i ' 의 값이 두 배가 되는 것이지요. 그 결과



로 나타남을 알 수 있습니다.


이제 그럼 조금 복잡한 코드를 살펴볼까요.


class gorp

{

vector<int> values;

int m_;

public:

gorp(int mod) : m_(mod) {}

gorp& put(int v){ values.push_back(v); return *this; }

int extras()

{

int count = 0;

for_each( values.begin(), values.end(),

[=,&count](int v){ count += v % m_; } );

return count;

}

};


gorp g(4);

g.put(3).put(7).put(8);

cout << "extras: " << g.extras();


사실 위 코드는 상당히 재미있는 코드입니다. extras 함수를 호출하면 람다가 각 원소를 4 로 나눈 나머지들의 합을 구해서 더해주는데요, 한 가지 궁금한점! 과연 람다에서 어떻게 m_ 을 capture 할 수 있었을까요? 람다는 여기서 암묵적으로 (implicit) 클래스의 this 를 Capture 했기 때문에 m_ 을 접근할 수 있었던 것입니다. 


따라서 위 코드는




으로 성공적인 결과를 보여줍니다.


이렇게 this 를 암묵적으로 Capture 할 수 있기에 아래와 같은 놀라운 일도 발생할 수 있습니다.


struct foo

{

foo() : i(0) {}

void amazing(){ [=]{ i=8; }(); }

int i;

};

foo f;

f.amazing();

cout << "f.i : " << f.i;


위 코드를 언뜻 보면 i 를 값으로 capture 햇는데 어떻게 8 을 대입할 수 있냐고 물을 수 있는데, 사실 this 를 Capture 해서 this.i = 8 을 통해 mutable 없이도 값을 바꿀 수 있습니다. 왜냐하면 분명 this.i = 8 에서 상수인 this 를 변경한 것은 아니기 때문이죠.



  Capture 의 범위
 


Capture 되는 개체들은 모두 람다가 정의된 위치에서 접근 가능해야만 합니다. 예를 들어


int i = 8;

{

int j = 2;

auto f = [=]{ cout << i/j; };

f();

}


의 코드는 람다의 위치에서 i, j 모두 접근 가능하기 때문에 Capture 가능하므로 정상적으로



가 나옵니다.


그렇다면 아래 코드는 어떨까요?


int i = 8;

auto f =

[i]()

{

int j = 2;

auto m = [=]{ cout << i/j; };

m();

};

f();


바깥의 람다에서 i 를 Capture 하였기에, 바깥의 람다 몸통 안에서 i 를 사용할 수 있겠지요. 따라서 내부의 람다는 i 를 Capture 할 수 있게 됩니다. 그렇기에, 위와 동일한 결과인




가 나오게 됩니다. 하지만, 만일 바깥의 람다에서 i 를 Capture 하지 않았다면 어떨까요.


int i = 8;

auto f =

[]()

{

int j = 2;

auto m = [=]{ cout << i/j; };

m();

};

f();


그러면 예상했던 대로 컴파일 오류 error C3493: 'i' cannot be implicitly captured because no default capture mode has been specified 가 나오게 됩니다. 


조금 더 복잡한 예로 아래의 코드를 살펴봅시다.


int i = 8;

auto f =

[=]()

{

int j = 2;

auto m = [&]{ i /= j; };

m();

cout << "inner: " << i;

};

f();

cout << " outer: " << i;


일단 바깥의 람다는 i 를 값으로 Capture 하였기 때문에 바깥의 람다(f) 몸통에서는 i 에 const 속성이 붙습니다. 그런데, 내부의 람다(m) 가 그 i 를 레퍼런스로 Capture 해서 값을 변경하려고 했습니다. 그렇다면, 당연히 오류가 나겠지요. 실제로 컴파일 오류 'i': a by-value capture cannot be modified in a non-mutable lambda 가 발생하게 됩니다.


이를 해결하려면, 당연히도 mutable 속성을 붙여주면 됩니다. 


int i = 8;

auto f =

[i]() mutable

{

int j = 2;

auto m = [&, j]() mutable{ i /= j; };

m();

cout << "inner: " << i;

};

f();

cout << " outer: " << i;


i 자체가 값으로 입력 되었기 때문에 outer i 의 값은 바뀌지 않고 8 로 남아 있고, 값으로 받은 i 가 m 에 의해서 2 로 나눠지므로 4 가 됩니다. 따라서, 그 결과 




로 나오게 되죠.



 

 클로져 객체의 복사 생성자와 소멸자

 


모든 클로져 객체들은 암묵적으로 정의된 복사 생성자(copy constructor)와 소멸자(destructor)를 가지고 있습니다. 이 때 클로져 객체가 복사 생성 될 때 값으로 Capture 된 것들의 복사 생성이 일어나겠지요. 아래의 예를 한번 보도록 하겠습니다.


일단 


struct trace

{

trace() : i(0) { cout << "construct\n"; }

trace(trace const &) { cout << "copy construct\n"; }

~trace() { cout << "destroy\n"; }

trace& operator=(trace&) { cout << "assign\n"; return *this;}

int i;

};


와 같이 생성, 복사 생성, 소멸, 그리고 대입 연산을 확인할 수 있는 trace 라는 구조체를 정의해놓고 


trace t;

int i = 8;


auto f = [=]() { return i / 2; };


를 한다면 어떻게 나올까요? f 에서 t 를 사용하지 않았으므로, t 를 Capture 하지 않게 됩니다. 따라서 그냥



이 나오게 됩니다.


그렇다면 아래의 예는 어떨까요


trace t;

int i = 8;


auto m1 = [=]() { int i = t.i; };

cout << " --- make copy --- " << endl;


auto m2 = m1;


먼저 m1 을 생성하면서, 람다가 t 를 Capture 하였으므로 t 의 복사 생성자가 호출되게 됩니다. 왜냐하면 값으로 받았기 때문이지요. 만일 레퍼런스로 받았다면 복사 생성자가 호출되지 않았을 것입니다 (확인해보세요!) 그리고 아래의 auto m2 = m1; 에서 클로져 객체의 복사 생성이 일어나는데, 이 때, 클로져 객체의 복사 생성자가 값으로 Capture 된 객체들을 똑같이 복사 생성 해주게 됩니다. 따라서 또 한번 t 의 복사 생성자가 호출되겠지요. 그 결과 아래와 같이 출력됩니다.






 

 람다의 전달 및 저장

 

람다를 저장 및 전달하는 방식으로 앞에서 두 가지 방법을 보았습니다. 바로


template<typename T> void foo(T f)

auto f = []{};


이지요. 우리가 만들어낸 클로져 객체의 타입이 정확히 무엇인지 몰라도 위와 같은 방법으로 성공적으로 처리할 수 있습니다.


또 다른 방법으로는 함수 포인터를 이용하는 방법이 있는데요, 이 경우 람다가 Capture 하는 것이 없어야만 합니다. 


typedef int(*f_type)(int);

f_type f = [](int i)->int{ return i+20; };

cout << f(8);


(참고로 위 기능은 Visual Studio 2010 에서 지원되지 않습니다 - 그 후의 버전에서만 가능합니다)


위 역시 성공적으로 28 을 출력함을 알 수 있습니다.


그런데, C++ 11 에서는 클로져 객체를 전달하고 또 저장할 수 있는 막강한 기능이 제공됩니다. 바로 std::function 인데요, 그 어떤 클로져 객체나 함수 등을 모두 보관할 수 있는 만능 저장소 입니다. (참고로 std::function 은 Visual Studio 2010 에서 <functional> 을 include 해야 합니다)


std::function< int(std::string const &) > f;

f = [](std::string const & s)->int{ return s.size(); };

int size = f("http://itguru.tistory.com");


cout << size << endl; 


std::function 은 위와 같이 std::function < 반환 타입 ( 인자 ) > 와 같은 형태로 쓰며, Capture 가 있어도 상관이 없습니다. 물론 위 코드는 실행하면 




와 같이 잘 나오지요.


이 std::function 을 통해 아래와 같이 재밌는 코드도 짤 수 있습니다.


std::function<int(int)> f1;

std::function<int(int)> f2 =

[&](int i)->int

{

cout << i << " ";

if(i>5) { return f1(i-2); }

};

f1 =

[&](int i)->int

{

cout << i << " ";

return f2(++i);

};

f1(10);


이것이 가능한 이유는 만일 auto 를 이용하였더라면 auto f1 을 한 시점에서 f1 이 명확히 구현이 되어야 컴파일러에서 타입을 추정할 수 있는데, 위와 같은 경우 f1 을 구현하려면 f2 를 먼저 구현해야 하고, 또 f2 를 구현하려면 다시 f1 을 먼저 구현해야 하는 순환적인 논리 딜레마에 빠지게 됩니다. 따라서 function 을 이용해서 f1 을 선언만 해 놓은 뒤, f2 를 구현하고, 다시 f1 을 구현하면 됩니다. 


위 코드를 실행하면




와 같이 잘 나옴을 알 수 있습니다.


마찬가지로 아래와 같은 재귀 호출 함수도 구현할 수 있습니다.


std::function<int(int)> fact;

fact =

[&fact](int n)->int

{

if(n==0){ return 1; }

else

{

return (n * fact(n-1));

}

};

cout << "factorial(4) : " << fact(4) << endl;


이 역시 auto 를 이용했더라면, 처음 Capture 부분에서 Capture 하는 대상의 타입이 명확히 정해지지 않은 상태이므로 컴파일러가 타입을 추정할 수 없게 됩니다. 하지만 function 을 이용해서 성공적으로 구현할 수 있습니다. 위 계산 결과는 당연히




가 나오겠지요.



  마치며
 


C++ 에 새롭게 추가된 람다는 기존의 C++ 과 전혀 다른 새로운 개념 입니다. 하지만 람다를 이용하면 수십줄의 코드도 한 두 줄로 간추릴 수 있는, 엄청난 기능이 아닐 수 없습니다. 


이제 여러분들 손에는 람다라는 막강한 도구가 주어졌습니다. 이를 어떻게 사용하느냐는 여러분의 몫이지요 :) 


그리고 이런 훌륭한 강의를 제공해주신 Michael Caisse 님에게 감사의 말을 전합니다. 

반응형
반응형
 

하드 코딩 (Hard - coding)

하드코딩: 설정사항이나 코드 등의 시스템적으로 사용하는 변수를 변수를 사용하지 않고 

              값을 직접 소스코드에 박아서 사용하는 방식을 말한다.

              코드가 바뀌었을 경우 자동으로 반영되지 않기 때문에 이후에 버그가 발생할 위기가 많은 시한폭탄 같은 방식이다.

               

1
2
3
4
5
6
int main()
{
    const char *filename = "C:\\myfile.txt";

    printf("Filename is: %s\n", filename);
}


위 예제 코드에서 'C:\\myfile.txt' 가 하드코딩에 해당한다.

만약 myfile.txt 의 경로가 C드라이브가 아닌 다른 경로에 있을경우 어떻할 것 인가? 

그때 마다 다시 코드를 수정하고 재 컴파일하는 번거로움을 감수할 것인가?




또다는 예로 구구단을 출력하는 프로그램을 짠다고 가정할때....

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1)

int a;

for(int i = 0; i < 9; i++)
{
printf("%d * %d = %d", a, (i + 1), a * (i + 1));
}

//의 방식으로 짤 수도 있겠지만

2)
if (a == 1)
{
printf("1 * 1 = 1");
printf("1 * 2 = 2");
...
printf("1 * 8 = 8");
printf("1 * 9 = 9");
}
if (a == 2)
{
...
}
if (a == 3)
...
...
...
if (a == 9)
{
printf("9 * 1 = 9");
printf("9 * 2 = 18");
...
printf("9 * 8 = 72");
printf("9 * 9 = 81");
}

//위와 같은 방식으로 짤수도 있는데 2)번과 같은 방식의 코딩이 하드코딩이다.




한마디로 하드 코딩이란 코딩 방식의 일종으로 외부 입력에 대한 유연성이 없고 확장성이 낮은 코딩을 말한다.

하지만 단점만 존재 하는 것은 아니며 코드의 목적이 매우 직관적이라 가독성도 괜찮은 편이고

컴퓨터의 입장에서는 자잘한 체크나 유효성 검사 등이 빠지고 반복문이 줄어들어 속도가 올라가므로

정말 절대로 변경되지 않을 것이라고 자신할 수 잇는 작은 부분에 한정적으로 사용하면 나름대로 

효과를 거둘 수 있기는 하다.


하지만 요즘 같은 시대는 하루에도 수십번 자주 변경이 일어나고 

외부 입력 자체도 다양한 프로그램을 많이 개발하는 시대이기 때문에 

하드코딩은 악의 근원지라는 표현에 걸맞는 방식이다.



http://codinglove.tistory.com/79





반응형
반응형

[기본 용어 설명]

- 바이트 패딩: 

몇몇 컴파일러는 구조체의 필드를 메모리에 위치시킬때 중간에 빈공간없이 쭉 이어서 할당하는 경우도 있지만 대부분의 컴파일러는 성능향상을 위해 CPU가 접근하기 쉬운 위치에 필드를 배치하는데(OS나 컴파일러에 따라 4바이트단위 또는 8바이트 단위로 끊는작업을 함) 이를 "바이트 패딩"이라고 하며, 그러다보니 중간이 빈 공간이 들어가게 되는데 이 빈 공간을 "패딩비트"라 한다.  

 

* 바이트패딩을 사용했을때 왜 성능향상에 도움을 줄까? 그 해답은 아래 링크를 통해 확인바람

http://pangate.com/19

 

이러한 바이트 패딩 때문에 구조체 정의를 어떻게 하느냐에 따라 구조체의 크기가 달라진다. 아래 예제를 살펴보자.

 

예)







 

- 위에서 보는것 처럼 구조체 ST_TEST1, ST_TEST2의 멤버변수는 동일하다. 하지만 변수의 선언 순서에 따라 바이트패딩이 적용되서 사이즈가 다르게 된다. 

바이트 패딩 의해

ST_TEST1 은

[char c1 (1) | empty(1) | short s1(2)]   ||   [int i1(4)]                    = 총 8바이트

ST_TEST2 는

[char c1(1) | empty(3)]   ||   [int i1(4)]   ||   [short s1(2) | empty(2)] = 총 12바이트

 

* empty : 빈공간, 즉 패딩비트 [] : 4바이트씩 모아놓은것.

* 실제 ST_TEST1 정렬방법(char 뒤에 1바이트 패딩비트가 생기는지)을 확인하고 싶으면 디버깅 - 창 - 조사식에 각 구조체의 변수의 주소를 확인해보면 된다. 아래 스샷을 보면 c1과 s1 사이에 1바이트 빈공간이 존재한다.  

 

 

이러한 문제를 해결할수 있는 방법이 바로 #paragma pack 이다.  

 

사용 방법은 2가지다.

 

1. #pragma pack(1)  - #pragma pack(4)

1바이트 정렬 방식이 필요할때 필요한 구문에서

#pragma pack(1) 을 써주고 (2바이트 정렬이 필요하다면 #paragma pack(2))

사용이 끝난 시점에서 다시 #pragma pack(4) 를 통해 바이트 정렬 방식을 4바이트로 되돌려 놓는다.

 

※  

Q: 왜 굳이 1바이트 정렬 후 다시 4바이트 정렬로 되돌려야하는가??

A:  PC의 CPU에 따라 레지스터를 읽는 바이트 사이즈가 다른데 32비트 운영체제의 경우 4바이트씩 읽는다 . 이는 성능과 연관있다. (자세한 내용은 컴퓨터 구조 공부를... ) 따라서 필요시에만 정렬방법을 바꿨다가 다시 기존 바이트 정렬 방식으로 돌려 놓아야한다.

 

2. #pragma pack(push, 1)  - #paragma pack(pop)

- #pragma pack(push, 1) 을 통해 구조체 정렬방식을 가지고 있는 스택에 1바이트 정렬방식을 push 를 통해 입력하고 ( 이 선언 이후부터는 정렬방식이 1바이트 바뀜)  사용 후에 이전 정렬방식( 이전 방식이 꼭 4 라는 법은 없음)되돌릴때 pop을 통해 이전에 push를 통해 입력했던 정렬방법(1)을 없앤다. 이를 위해 #pragma pack(pop) 을 사용한다.

* push 와 pop 이 뭔지 생소하다면 스택(Stack)에 대해 공부할것.

 

사용 예)

 

- 스샷 화면 속 빨간 네모 속 ST_TEST2 사이즈를 보면 7로 변해있는것을 확인할수 있다.

#paragma pack 을 쓰기 전에는 12바이트.

 

★ 그렇다면 가장 중요한 포인트! 이 #pragma pack 어디다 쓰면 유용한 건데???

moon_and_james-11

- 네트워크를 통한 구조체 전송시 바이트 패딩은 중요하다!

 

예) 만약 A시스템(32비트) B시스템(64비트)라고 가정, A시스템에서 B시스템으로 아래와 같은 구조체 패킷을 보낸다고 하면

struct ST_TEST

{

char c1;

int i1;

};

B 시스템은 바이트 패딩에 의해 A시스템과는 달리 8바이트단위로 끊는다. 따라서 첫번째 c1값만 정상이고 나머지 값들은 이상한값이 되버린다.

따라서 A시스템, B시스템 모두 바이트 패딩을 없애야 이러한 문제를 없앨수 있는데 이 해결방법이 위에 설명했던 #paragma pack 이다.  



http://blog.naver.com/dkdldhekznal/220206729625

반응형
반응형

WinCE라는 넘은 간혹 참 당확 스럽게 하는 경우가 있다.

 

일반 적으로 Release Mode에서는 해당(내가 작업할려는 Driver혹은 Sources가 있는 어떤 거라도) Optimize 가 Enable이 된다.

 

그런데 간혹 Driver작업이나 좀더 Critical한 Kernel작업을 하다가 보면 문제를 발생 시키기도 한다.

 

예를 들어 특정 Chips에서 아래와 같은 작업이 반드시 필요하다고 한다면

A = 0;

A = 1;

A = 0;

WinCE Compiler는 워낙 탁월한(?) 넘이라 "어라 결국 A=0 이내" 하고 위의 두 Code를 날려 버리는 경우가 발생한다.

 

일반 Code라면 뭐 그렇지만 Chip Register라던가 Peri.의 Reset신호라던가 할경우 위의 3 Code가 모두 필요한 상황인데,

 

간혼 최종 Code만 남는 경우가 발생하게 된다.

 

이럴 경우 해당 드라이버의 Optimize 를 Disable 할수도 없고 (물론 CONLY_FLAGS=$(CONLY_FLAGS) /Od 으로 할수도 있지만.

 

Optimize함으로 써 얻는 이익은 모드 포기 해야한다는, 쩝...)

 

그럴 경우 해당 루틴이있는 Function에 대해서만 Opimize Disable을 할수있는 방법을 WinCE는 제공한다.(방법 을 제공한다는 것은

 

결국 저런일이 빈번하게 일어날수 있다는 것을 시인하는 것일까? )

 

방법 : 아래와 같이 하면 끝

#pragma optimize( "g", off )

void example_function(void)

{

 .....

A = 0;

A = 1;

A = 0;

}

#pragma optimize( "g", on)

 

PS : 해당 Code가 정상적으로 생성되어 컴파일 되어 있는지는 해당 파일을 Assembly 파일로 만들어서 해당 Code를 확인하면 된다.

ms-help://MS.WindowsCE.500/wcepbguide5/html/wce50confilealternatives.htm

sources에 추가 : CONLY_FLAGS=$(CONLY_FLAGS) /FA




http://seungwan76.blog.me/140109794967



반응형
반응형


람다 함수 활용(1)

지금까지 First-Class Object, Higher-Order Function, Closure와 같은 조금은 지루한 개념들을 살펴봤습니다. 이런 개념들을 설명하지 않으면 람다 함수를 함수 포인터나 함수 객체를 대체하는 Syntactic Sugar로 오해할 수도 있기 때문입니다. 또 어떻게 람다 함수를 일반 변수처럼 사용할 수 있는지 배경 지식을 설명하기 위해서였습니다. 

그럼 이 람다 함수를 어디에 사용하면 좋을까요?앞으로 4가지 활용 예를 살펴 보겠습니다.


① 지연 호출(Deferred Call)에 활용
vector와 같은 자료구조에 저장한 후에 필요한 특정 시점에 호출하는 것입니다.
아래 예제에서는 람다 함수를 이용해 Task를 만들고 TaskManager라는 컨테이너 클래스를 통해 특정 시점에 람다 함수가 실행되는 것을 나타내고 있습니다.

TaskManager manager;

 

// TaskManager에 Task로 저장한다.

manager.AddTask( [](){ cout << “task1” << endl;} );

manager.AddTask( [](){ cout << “task2” << endl;} );

 

 

// 같은 스레드 혹은 다른 스레드에서 실행시킨다.

manager.Run();



② 비동기 호출과 결과 코드의 응집성을 높이는 데 활용

비동기 처리 코드의 문제점은 비동기 요청 함수를 호출하는 곳과 결과를 처리하는 함수가 동떨어져 있어 로직 흐름을 파악하기 어렵다는 것입니다. 비동기 요청 함수를 호출할 때 결과 처리에 대한 코드를 람다 함수로 구현해 파라미터로 전달하면 코드 응집성이 높아질 수 있습니다.
아래 예제에서는 Client 클래스의 요청에 대한 처리가 비동기적으로 이뤄질 때 람다 함수를 이용해 요청 시점에 결과를 어떻게 처리할 것인지 기술하는 것을 나타내고 있습니다.

 

class Client

{

public:

// 비동기 요청에 대한 결과를 처리할 람다 함수 타입

typedef function<void(int result)> AsynResultProcessor;

 

public:

void AsyncRequest(AsynResultEvent resultEvent)

{

resultEvent_ = resultEvent;

}

 

void Process()

{

Result_ = 1004;

 

// 미리 저장된 결과 처리 람다 함수를 호출한다.

resultProcessor_(result_);

}

private:

AsynResultProcessor    resultProcessor_;

int                      result_;

};

 

Client client;

 

// 응답을 어떻게 처리할 것인지 요청 시 기술할 수 있다.

client.AsyncRequest( [](int result)

{

// 어떻게 결과를 처리할 것인지 기술

cout << result << endl;

});

 

// 특정 시점에 처리한다.

client.Process();




③ 일회성 함수를 쉽게 구현하기 위해 활용

특히 STL을 이용하는 데 유용합니다. STL 알고리즘 함수들의 입력 파라미터로 람다 함수를 넘겨주면 따로 함수 객체를 정의하는 번거로움이 사라지고 코드 응집성이 높아지므로 STL 함수의 작동을 더 쉽게 이해할 수 있습니다. 아래 예제에서는 함수 객체와 람다 함수 이용을 비교해서 보여주고 있습니다.

 

//  for_each()를 위한 함수 객체

//  일회성 호출을 위해 많은 코드가 필요하고 가독성도 떨어진다.

struct LambdaFunctor

{

void operator()(int n) const

{

cout << n << " ";

}

};

 

int main()

{

vector<int> v;

 

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

{

v.push_back(i);

}

 

// 1. 함수 객체를 이용한 코드

for_each(v.begin(), v.end(), LambdaFunctor());

 

// 2. 람다 함수를 이용한 코드

for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

 

cout << endl;

}




포스팅이 길어졌네요~ 
다음 글에서 람다 활용에 대해 마저 이야기 하고 "람다 이야기" 시리즈를 마치도록 하겠습니다.



http://tedahn.tistory.com/24







람다 함수 활용(2)

앞선 포스팅에서 람다 함수의 캡쳐(Capture) 기능에 대해 설명 했었습니다. (
여기! 있습니다)
캡쳐 기능을 사용 하면 다음과 같이 활용 할 수 있습니다.

④ 템플릿을 대체하는 데 활용
람다 함수의 캡처 기능을 사용하면 람다 함수 몸체에서 외부 변수들을 마음껏 사용할 수 있을 뿐만 아니라 클로저(Closure)가 되어 함께 묶입니다. 이 기능을 활용하면 기존에 파라미터 타입과 개수 처리를 일반화하기 위해 사용하던 템플릿 사용을 지양할 수 있습니다. 아래 두 함수가 템플릿과 람다 함수를 이용해서 원하는 시점에 호출 되게 하려면 어떻게 하면 될까요?

// 템플릿 객체와 람다 함수를 이용해 간접적으로 호출될 예제 함수들


void Function_Arg1(int arg1)

{

cout << "Function_Arg1 : " << arg1 << endl;

}

 

void Function_Arg2(int arg1, const char* arg2)

{

cout << "Function_Arg2 : " << arg1 << "," << arg2 << endl;

}



위 두 함수를 특정 파라미터와 묶어 특정 시점에 호출해서 사용하고 싶을 경우 템플릿을 이용하면 아래와 같은 방법으로 사용할 수 있습니다.

// 템플릿 객체를 이용해 함수를 인자와 함께 묶어 특정 시점에 호출하는 예


int arg1 = 1004;

const char* arg2 = "Lambda!";

 

vector<ITask*> taskList;

 

// 1. 컨테이너에 담는다.

taskList.push_back( new Task_1<void, int>(&Function_Arg1, arg1) );

taskList.push_back( new Task_2<void, int, const char*>(&Function_Arg2, arg1, arg2) );

// 2. 특정 시점에 컨테이너를 순회하며 실행시킨다.

for( auto i = taskList.begin(); i != taskList.end(); ++i )

{

(*i)->Do();

}



위와 같은 코드로 템플릿 객체를 사용하려면 다음과 같은 코드가 필요합니다.

// 파라미터 1개짜리 함수를 담기 위한 템플릿 클래스


template<typename RetType, typename ArgType1>

class Task_1 : public ITask

{

typedef function<RetType(ArgType1)> FunctionType;

 

public:

Task_1(FunctionType f, ArgType1 a1)

: function_(f), arg1_(a1)

{

}

 

virtual void Do()

{

cout << "Task_1::Do()" << endl;

function_(arg1_);

}

private:

FunctionType   function_;

ArgType1       arg1_;

};

 

// 파라미터 2개짜리 함수를 담기 위한 템플릿 클래스

template<typename RetType, typename ArgType1, typename ArgType2>

class Task_2 : public ITask

{

typedef function<RetType(ArgType1,ArgType2)> FunctionType;

 

public:

Task_2(FunctionType f, ArgType1 a1, ArgType2 a2)

: function_(f), arg1_(a1), arg2_(a2)

{

}

 

virtual void Do()

{

cout << "Task_2::Do()" << endl;



실제 C++ 프로젝트를 진행하다 보면 위와 비슷한 형태의 템플릿 클래스를 자주 구현하게 됩니다. 이 때 발생하는 문제점은 함수의 파라미터가 늘어날 때마다 템플릿 클래스를 추가해 줘야 하고 코드가 직관적이지 않다는 점입니다. BOOST_PP를 이용하면 자동화할 수 있지만 디버깅이 굉장히 어렵고 작성자만 이해할 수 있는 코드가 만들어지곤 합니다. 때론 작성자도 이해 못하죠 ;)

그럼 이런 상황에서 람다 함수를 이용하면 어떨까요? 
아래 예제를 보면 람다 함수의 캡처 기능을 이용해 깔끔하게 구현되는 것을 볼 수 있습니다. 뿐만 아니라 실행시킬 함수의 파라미터가 몇 개든 타입이 무엇이든 추가되는 코드는 없습니다. 앞으로 많은 부분에서 람다 함수를 이용해 템플릿 사용을 줄일 방안이 제안 되길 기대해 봅니다.


// 람다 함수를 이용해 함수와 인자를 묶어 특정 시점에 호출하는 예제


int arg1 = 1004;

const char* arg2 = "Lambda!"; 

...
...
typedef function<void(void)> LambdaType;


vector<LambdaType> lambdaList;

 

lambdaList.push_back( [=](){ Function_Arg1(arg1); } );

lambdaList.push_back( [=](){ Function_Arg2(arg1, arg2); } );

 

for_each( lambdaList.begin(), lambdaList.end(), [](LambdaType lambda)

{

lambda();

});




마치면서

4회에 걸쳐『Plus C++0x』람다(Lambda) 이야기를 했습니다. 막연히 람다(Lambda) 의 기능에 대해 설명 하기 보다는 이면에 깔린 배경 개념을 소개 함으로써 현대 프로그래밍 언어가 갖는 특징을 이야기 하고 싶었습니다.

다음 시리즈에서는 우측 값 참조(RValue Reference)에 대해 알아보고 C++0x에서 어떤 의미를 갖는지를 설명하려고 합니다. 람다(Lambda) 관련해서는 많은 내용을 한 번에 준비해서 쓰려니 고생스럽더군요. 이번엔 차근차근 포스팅 하면서 글을 완성할 수 있으면 좋겠네요 ;)

( 마이크로소프트웨어 6월호의 『생각의 직관적인 표현, 람다(Lambda)』를 보시면 보다 잘 정리 되고 추가 된 내용을 보실 수 있습니다. )


고자료
1. MSDN - 
http://msdn.microsoft.com/en-us/library/dd293608.aspx
2. MSDN - http://msdn.microsoft.com/en-us/library/dd293599.aspx
3. MSDN - http://channel9.msdn.com/posts/kmcgrath/Lambda-Expressions-in-C/
4. MSDN - http://blogs.msdn.com/vcblog/archive/2008/11/18/stupid-lambda-tricks.aspx
5. Wikipedia - http://en.wikipedia.org/wiki/First-class_function
6. Wikipedia -  http://en.wikipedia.org/wiki/Higher-order_function

7. VSTS 2010 Team Blog -  http://vsts2010.net/category/Language%20Development/C++0x


http://tedahn.tistory.com/25

반응형
반응형


프로퍼티 value


키워드 value는 일반 속성 선언의 set 접근자에 사용되며, 메서드의 입력 매개 변수와 비슷합니다. 단어 value는 클라이언트 코드에서 속성에 할당하는 값을 참조합니다. 다음 예제에서 MyDerivedClass에는 Name이라는 속성이 있습니다. 이 속성은 value 매개 변수를 사용하여 지원 필드 name에 새 문자열을 할당합니다. 클라이언트 코드 측면에서 연산은 단순 할당으로 작성됩니다.

C#
class MyBaseClass
{
    // virtual auto-implemented property. Overrides can only
    // provide specialized behavior if they implement get and set accessors.
    public virtual string Name { get; set; }

    // ordinary virtual property with backing field
    private int num;
    public virtual int Number
    {
        get { return num; }
        set { num = value; }
    }
}


class MyDerivedClass : MyBaseClass
{
    private string name;

   // Override auto-implemented property with ordinary property
   // to provide specialized accessor behavior.
    public override string Name
    {
        get
        {
            return name;
        }
        set
        {
            if (value != String.Empty)
            {
                name = value;
            }
            else
            {
                name = "Unknown";
            }
        }
    }

}


https://msdn.microsoft.com/library/a1khb4f8(v=vs.110).aspx

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

using 과 Dispose()  (0) 2017.07.19
확장메서드 public static class  (0) 2017.07.18
정적 생성자가 호출되는 시점  (0) 2015.11.14
AssemblyInfo.cs  (0) 2015.11.11
객체 배열 new , new  (0) 2015.11.11
반응형
  • 정적 생성자는 액세스 한정자를 사용하지 않고 매개 변수를 갖지 않습니다.

    • 정적 생성자는 첫 번째 인스턴스가 만들어지기 전이나 정적 멤버가 참조되기 전에 클래스를 초기화하기 위해 자동으로 호출됩니다.

    • 정적 생성자는 직접 호출할 수 없습니다.

    • 사용자는 프로그램에서 정적 생성자가 실행되는 시기를 제어할 수 없습니다.

    • 정적 생성자는 일반적으로 클래스에서 로그 파일을 사용할 때 이 파일에 항목을 쓰기 위해 사용됩니다.

    • 정적 생성자는 생성자에서 LoadLibrary 메서드를 호출할 수 있는 경우 비관리 코드에 대한 래퍼 클래스를 만들 때도 유용합니다.

    • 정적 생성자에서 예외를 throw하면 런타임은 다시 이 생성자를 호출하지 않으므로 해당 형식은 프로그램이 실행되는 응용 프로그램 도메인의 수명 동안 초기화되지 않은 상태로 남아 있게 됩니다.

  class test

    {

        public static test tt;

        

        static test()

        {

            Console.WriteLine("정적 생성자");

            tt = new test();

        }

        public test()

        {

            Console.WriteLine("생성자");

        }

    }


    class Program

    {

        static void Main(string[] args)

        {


1.  test asdf1 = new test();        //  기본 생성자가 불리기 이전에 static test() 정적 생성자가 한번 불린다


또는


2. test asdf = test.tt;  // 위 1. 구분을 주석처리했을때는 이 처럼 정적멤버 tt 가 참조 되기 이전에 static test()

 가 호출된다




reference

https://msdn.microsoft.com/ko-kr/library/windows/apps/xaml/k9x6w0hc(v=vs.100).aspx



반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

확장메서드 public static class  (0) 2017.07.18
프로퍼티 value  (0) 2015.11.17
AssemblyInfo.cs  (0) 2015.11.11
객체 배열 new , new  (0) 2015.11.11
추상함수(abstract)  (0) 2015.11.11
반응형

가상함수를 생성자와 소멸자에 넣으면 호출이 안된다고 되어있어서

실험삼아 책에 나와있는 코드를 테스트 해 보았는데 호출이 되었습니다. -_-;;

그리고 생성자와 소멸자에 가상함수를 쓰면 안된다고 나와있고 거기에 대한 자세한

설명이 없더군요. 이런저런 고민을 해 보았는데 왜 쓰면 안되는지 모르겠습니다.

코드는 대충..

class A
{
public:
A(){
test();
}
~A(){
test();
}
virtual test()
{
printf("test\n");
}
};

void main()
{
A a;
}

출력
test
test

간단하게 이정도네요.
왜 호출이 되고 왜 쓰면 안되는지 설명 부탁드립니다.


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


이펙티브 C++ 참고하세요.
항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자

대충 요약하면 자식이 생기기도 전에 자식 함수 호출하면 안되는 거고
자식이 사라진 다음에 자식 함수 호출 하면 안되서 그렇습니다.




https://kldp.org/node/98424

반응형
반응형

AssemblyInfo.cs

이 파일은 프로젝트와 관계되는 어셈블리의 정보가 담겨 있는 CS 파일입니다. 어셈블리는 하나의 기능을 생성하는 타입과 리소스의 집합체입니다. 하나의 프로젝트를 만든다는 것은 하나의 어셈블리를 만드는 것을 의미합니다.

 

[assembly: ...]는 어셈블리의 정보를 설정하기 위해 사용됩니다. AssemblyInfo.cs에서 설정되는 내용들은 다음과 같습니다.

 

어셈블리 항목

설명 

AssemblyTitle 어셈블리의 애칭(friendly name)
AssemblyDescription 어셈블리의 정보를 간략히 설명
AssemblyConfiguration 
AssemblyCompany 어셈블리를 출하(ship)하는 회사의 이름
AssemblyProduct 제품의 이름
AssemblyCopyright 어셈블리의 저작권 정보
AssemblyTrademark 트레이드마크 정보
AssemblyCulture 어셈블리의 대상 문화권을 의미하는 일람표(enumeration)
ComVisible 이 어셈블리를 COM 컴포넌트들에 노출할지를 결정
Guid 어셈블리의 GUID
AssemblyVersion 문자열로 표현된 버전 정보
AssemblyFileVersion

 Win32 파일 버전을 명시하는 문자열. 기본적으로 AssemlyVersion과

 동일하게 설정

 

프로젝트를 빌드하여 실행 파일을 만든 후 탐색기를 통해 어셈블리의 정보를 확인할 수 있습니다. 방법은 다음과 같습니다.

 

1. 탐색기를 실행한 후 프로젝트 폴더로 갑니다.

2. bin 폴더를 열고, Release 폴더를 선택합니다.

 

 

3. 실행 파일의 아이콘을 마우스 오른쪽 버튼으로 누릅니다.

4. 팝업 메뉴의 최하단에 있는 속성을 택합니다.

 

 

5. 속성 대화상자가 뜬 후 '자세히' 탭을 선택합니다.

6. 5를 실행한 후 나타나는 테이블이 어셈블리 정보의 일부를 보여 줍니다.

 

 

어셈블리 정보를 보여주는 위 그림에서 속성들이 어떤 어셈블리 항목들과 관계되는지 아래 표로 정리합니다.

 

속성

어셈블리 속성 

파일 설명

AssemblyTitle

파일 버전

AssemblyFileVersion

제품 이름

AssemblyProduct

제품 버전

AssemblyFileVersion

저작권

AssemblyCopyright



http://netrance.blog.me/110091707935


반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

프로퍼티 value  (0) 2015.11.17
정적 생성자가 호출되는 시점  (0) 2015.11.14
객체 배열 new , new  (0) 2015.11.11
추상함수(abstract)  (0) 2015.11.11
GetHashCode()  (0) 2015.11.11
반응형
클래스의 객체 또한 배열로 생성할 수 있다. 하지만 값 타입과 차이점이 있기 때문에 정확하게 짚고 넘어가자. 값 타입 배열을 만들면 메모리가 배열의 선언과 동시에 생성된다. 하지만 객체 배열을 만들면 객체의 이름만 첨자 수 만큼 만들어 질 뿐 객체 자체의 메모리는 생성되지 않는다. 즉 객체의 메모리는 따로 생성해주어야 한다. using System; class Animal { private String kind; private String name; public Animal(String kind, String name) { this.kind = kind; this.name = name; } public override string ToString() { return kind + " : " + name; } } public class ObjectArrayTest01 { static void Main() { Animal[] a = new Animal[3]; for (int i = 0; i < a.Length; i++) { try { Console.WriteLine("Animal[" + i + "]:" + a[i] + ", HashCode:" + a[i].GetHashCode()); } catch (NullReferenceException e) { Console.WriteLine(e); } } } } Animal 클래스형의 객체 배열을 생성하고, for문을 사용해서 배열 요소들의 해시코드를 출력한다. 이때 NullReferenceException엘에러가 발생한다. 이것은 null 객체를 참조했기 때문에 발생하는 에러이다. 메모리가 생성되지 않은 상태에서 객체를 참조하기 때문이다. 해시코드는 메모리가 생성될 때 객체에 부여되는 32비트 정수값으로 서로 다른 객체는 같은 해시코드를 부여받을 수 없다. using System; class Animal { private String kind; private String name; public Animal(String kind, String name) { this.kind = kind; this.name = name; } public override string ToString() { return kind + ":" + name; } } public class ObjectArrayTest02 { static void Main() { Animal[] a = new Animal[3]; a[0] = new Animal("개", "진돌이"); a[1] = new Animal("고양이", "나비"); a[2] = new Animal("돼지", "꿀꿀이"); for (int i = 0; i < a.Length; i++) { Console.WriteLine("Animal[" + i + "]:" + a[i] + ", HashCode:" + a[i].GetHashCode()); } } } 각각의 배열 요소에 new 연산자를 사용해서 객체를 생성하고 해시코드를 출력하였다. 이번에는 에러가 발생하지 않고 각각의 배열요소가 해시콜드를 출력한다. 객체 배열은 같은 클래스 타입의 객체를 여러개 생성할 때 사용한다. 따라서 그 배열 요소들이 하나의 객체이기 때문에 일반 클래스를 생성할 때처럼 new 연산자를 사용해서 메모리를 생성해야 한다. 객체 배열은 배열의 생성과 메모리의 할당이 분리되어 있다 http://blog.naver.com/khagaa/30097427182
반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

정적 생성자가 호출되는 시점  (0) 2015.11.14
AssemblyInfo.cs  (0) 2015.11.11
추상함수(abstract)  (0) 2015.11.11
GetHashCode()  (0) 2015.11.11
C# Linq 1강 - Linq 맛보기  (0) 2015.09.10
반응형
// 추상함수(abstract 를 쓴, c++ 로 치면 순수 가상함수)가 하나라도 있는 클래스는
// 추상클래스(class 앞에 abstract 를 붙인)가 되어야 한다.

// cf) abstract 함수나 virtual 함수를 override 할 때 접근 제한자는 바꿀 수 없으며
// 생각해 보면 당연하겠지만, 추상/가상 함수는 protected 와 public 만 가능하다.
// 또한 추상클래스를 상속받은 경우 모든 추상함수를 '반드시' override 해야만 한다.

abstract class Emotion
{
 public float deep;

 // 추상함수
 protected abstract void Feel();

 public virtual void Express()
 {
 }
}

class Pleasure : Emotion
{
 protected override void Feel()
 {
 }

 public override void Express()
 {
 }
}


http://blog.naver.com/herbbread/220000818153


반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

AssemblyInfo.cs  (0) 2015.11.11
객체 배열 new , new  (0) 2015.11.11
GetHashCode()  (0) 2015.11.11
C# Linq 1강 - Linq 맛보기  (0) 2015.09.10
c# XML 문서 주석(C# 프로그래밍 가이드) /// <summary>  (0) 2015.09.02
반응형

http://blog.naver.com/rh0969/166432974


<< GetHashCode() >>
 - 객체를 식별하는 고유한 ID
 - GetHashCode()에 의해서 반환
 - 사용자가 GetHashCode()를 재정의해서 사용할 수 있음


    class HashBase
    {
        private int hash;
        public HashBase(int h)
        {
            hash = h;
        }
    }

    class HashTest
    {
        public static void Main()
        {
            HashBase h1 = new HashBase(10);
            HashBase h2 = new HashBase(10);

            Object obj = new Object();
            string s = "hahaha";
            string t = "hahaha";
            int i = 10000;
            int j = 10000;
            Console.WriteLine("클래스 객체 h1 => " + h1.GetHashCode());
            Console.WriteLine("클래스 객체 h2 => " + h2.GetHashCode());
            Console.WriteLine("Object 객체 obj => " + obj.GetHashCode());
            Console.WriteLine("string 변수 s => " + s.GetHashCode());
            Console.WriteLine("string 변수 t => " + t.GetHashCode());
            Console.WriteLine("int형 변수 i => " + i.GetHashCode());
            Console.WriteLine("int형 변수 j => " + j.GetHashCode());
        }
    }

[출처] [C#]GetHashCode()|작성자 착한남자



반응형
반응형

#include <iostream>

class A{

public :

void add(int a){

}

void add(int a, int b){

void (A::*fn)(int) = &A::add;

void (A::*fn1)(int,int,int) = &A::add;

printf("%x\t%x", fn,fn1);

}

void add(int a, int b, int c){

}

};


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

{

A ai;

A ai1;

A ai2;

ai.add(1,3);

std::cout << std::endl;

ai1.add(1, 3);


std::cout << std::endl;

ai2.add(1, 3);


return 0;

}


동일한 함수의 주소가 나옴을 알 수 있다





반응형
반응형

http://www.soen.kr/

.더블 버퍼링 활용

화면 깜박임이 발생하는 원인에 대해 연구해 보았고 그 해결책으로 더블 버퍼링이라는 멋진 방법을 소개했다. 그렇다면 더블 버퍼링을 과연 언제 어떻게 사용해야 잘 활용했다고 소문이 날까? 더블 버퍼링의 용도는 꼭 화면 깜박임을 제거하는데만 있는 것은 아니다. 내부 버퍼에서 틈틈이 작업을 할 수 있으므로 아이들(Idle) 시간을 활용하기 위해서 사용할 수도 있고 내부 버퍼를 외부 버퍼보다 더 크게 만들어 스크롤에 활용할 수도 있다.

여기서는 더블 버퍼링의 원리에 대해서만 이해하도록 하고 실무를 할 때 더블 버퍼링을 쓰면 좋겠다는 생각이 들면 적극적으로 활용해 보기 바란다. 다음 예제는 더블 버퍼링을 활용한 갱 화면이다. 갱(Gang) 화면이란 프로그램 제작자를 소개하는 용도를 가지며 일반적으로 숨겨져 있지만 제작자 자신을 표현한다는 면에 있어 다소 멋을 좀 부리는 경향이 있다. 이 예제는 배경 비트맵을 깔고 그 위에서 제작자 목록을 위로 스크롤하는 예를 보여준다.

 

#include "resource.h"

HBITMAP hBit, hBaby;

TCHAR szGang[]="Gang Version 1.0\r\n\r\n 감독 :  정수\r\n"

"개발자 :  상형\r\n사진 모델 :  한슬\r\n협찬 :  미영";

int my;

 

void DrawBitmap(HDC hdc,int x,int y,HBITMAP hBit)

{

HDC MemDC;

HBITMAP OldBitmap;

int bx,by;

BITMAP bit;

 

MemDC=CreateCompatibleDC(hdc);

OldBitmap=(HBITMAP)SelectObject(MemDC, hBit);

 

GetObject(hBit,sizeof(BITMAP),&bit);

bx=bit.bmWidth;

by=bit.bmHeight;

 

BitBlt(hdc,0,0,bx,by,MemDC,0,0,SRCCOPY);

 

SelectObject(MemDC,OldBitmap);

DeleteDC(MemDC);

}

 

void OnTimer()

{

RECT crt;

HDC hdc,hMemDC;

HBITMAP OldBit;

HFONT font, oldfont;

RECT grt;

int i,j;

 

GetClientRect(hWndMain,&crt);

hdc=GetDC(hWndMain);

 

if (hBit==NULL) {

    hBit=CreateCompatibleBitmap(hdc,crt.right,crt.bottom);

}

hMemDC=CreateCompatibleDC(hdc);

OldBit=(HBITMAP)SelectObject(hMemDC,hBit);

 

DrawBitmap(hMemDC,0,0,hBaby);

SetBkMode(hMemDC,TRANSPARENT);

 

font=CreateFont(30,0,0,0,0,0,0,0,HANGEUL_CHARSET,3,2,1,

    VARIABLE_PITCH | FF_ROMAN,"궁서");

oldfont=(HFONT)SelectObject(hMemDC,font);

 

my--;

if (my==20) {

    KillTimer(hWndMain,1);

}

 

SetTextColor(hMemDC,RGB(192,192,192));

for (i=-1;i<=1;i++) {

    for (j=-1;j<=1;j++) {

       SetRect(&grt,10+i,my+j,400+i,my+300+j);

       DrawText(hMemDC,szGang,-1,&grt,DT_WORDBREAK);

    }

}

 

SetTextColor(hMemDC,RGB(32,32,32));

SetRect(&grt,10,my,400,my+300);

DrawText(hMemDC,szGang,-1,&grt,DT_WORDBREAK);

 

SelectObject(hMemDC,oldfont);

DeleteObject(font);

 

SelectObject(hMemDC,OldBit);

DeleteDC(hMemDC);

ReleaseDC(hWndMain,hdc);

InvalidateRect(hWndMain,NULL,FALSE);

}

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

HDC hdc,hMemDC;

PAINTSTRUCT ps;

HBITMAP OldBit;

RECT crt;

 

switch(iMessage) {

case WM_CREATE:

    hBaby=LoadBitmap(g_hInst,MAKEINTRESOURCE(IDB_BITMAP1));

case WM_LBUTTONDOWN:

    SetTimer(hWnd,1,70,NULL);

    my=310;

    return 0;

case WM_TIMER:

    OnTimer();

    return 0;

case WM_PAINT:

    hdc=BeginPaint(hWnd, &ps);

    GetClientRect(hWnd,&crt);

    hMemDC=CreateCompatibleDC(hdc);

    OldBit=(HBITMAP)SelectObject(hMemDC, hBit);

    BitBlt(hdc,0,0,crt.right,crt.bottom,hMemDC,0,0,SRCCOPY);

    SelectObject(hMemDC, OldBit);

    DeleteDC(hMemDC);

    EndPaint(hWnd, &ps);

    return 0;

case WM_DESTROY:

    if (hBit) {

       DeleteObject(hBit);

    }

    DeleteObject(hBaby);

    PostQuitMessage(0);

    KillTimer(hWnd,1);

    return 0;

}

return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

실행 결과는 다음과 같다. 움직이는 화면이므로 직접 실행해 봐야 결과를 볼 수 있다. 예쁜 아기 그림이 있고 아래에서 문자열이 천천히 위로 올라오는 동작을 한다.

  

문자열은 바깥쪽에 회색 테두리를 가지도록 했으며 보기 편하도록 큼직한 폰트를 사용했다. 비트맵 위에서 글자가 움직이지만 깜박임은 전혀 없으며 아주 부드럽게 스크롤되는 것을 볼 수 있다. 만약 이런 화면을 더블 버퍼링으로 처리하지 않는다면 배경 비트맵과 그림이 계속 반복적으로 화면에 나타나기 때문에 깜박임이 심해지고 갱 화면으로서 가치가 떨어질 것이다.

좀 더 코드를 작성한다면 글자들이 오른쪽에서 왼쪽으로 한 줄씩 날라 오도록 할 수도 있고 점점 확대되는 모양으로 만들 수도 있다. 또는 약간의 애니메이션을 첨가한다거나 글자의 색상을 조작하여 Fade In, Fade Out 등의 장면 전환 효과를 낼 수도 있다. 아뭏든 더블 버퍼링을 쓰기만 하면 어떠한 모양도 깔끔하게 화면으로 구현할 수 있으므로 기발한 상상력을 발휘해 볼만하다.

반응형
반응형

http://www.soen.kr/


.더블 버퍼링

화면 출력의 깜박임을 해결할 수 있는 근본적이고도 완전한 방법은 더블 버퍼링(Double Buffering)뿐이다. 더블 버퍼링이란 용어 그대로 버퍼를 두 개(또는 그 이상) 사용하는 방식인데 화면에 보여줄 버퍼와 내부 계산에 사용할 버퍼를 따로 유지한다. 내부 버퍼에 미리 그림을 그린 후 화면 버퍼로 고속 전송하며 그리는 중간 과정을 숨겨진 내부 버퍼에서 처리함으로써 사용자는 최종 결과만 보도록 하는 기법이다.

내부 버퍼에서 일어나는 일은 사용자에게 보이지 않기 때문에 그림이 아무리 복잡해도, 화면을 다 지운 후 다시 그리더라도 깜박임을 전혀 목격할 수가 없다. 뿐만 아니라 그리는 순서에 따라 이미지간의 수직적인 아래 위를 지정할 수 있으며 여러 개의 이미지를 동시에 움직이는 것도 아주 부드럽게 처리할 수 있다.

더블 버퍼링에 사용되는 내부 버퍼는 구체적으로 메모리 영역인데 이 메모리 영역은 외부 버퍼, 즉 화면의 포맷과 호환되어야 한다. 그래야 내부 버퍼에 그린 그림을 별도의 조작없이 외부 버퍼로 고속 전송할 수 있다. 과거 DOS 시절에는 내부 버퍼를 비디오 램의 물리적인 포맷대로 작성한 후 비디오 램으로 곧바로 전송하는 방식을 사용했었다. 또는 아예 비디오 카드가 하드웨어적으로 여러 개의 페이지를 제공하여 페이지를 교체하는 방식을 사용하기도 했다.

윈도우즈에서는 내부 버퍼를 메모리에 직접 작성할 필요가 없는데 왜냐하면 비트맵이 내부 버퍼 역할을 해 주기 때문이다. 화면 DC와 호환되는, 즉 색상 포맷이 같고 크기가 동일한 비트맵을 생성한 후 이 비트맵에 그림을 그리면 비트맵 자체가 내부 버퍼 역할을 하게 된다. 비트맵에 그려진 그림을 화면으로 전송할 때는 물론 BitBlt 함수를 사용한다. 앞에서 만든 Bounce 예제를 더블 버퍼링으로 다시 작성해 보자.

 

#define R 20

int x,y;

int xi,yi;

HBITMAP hBit;

void OnTimer()

{

RECT crt;

HDC hdc,hMemDC;

HBITMAP OldBit;

HPEN hPen,OldPen;

HBRUSH hBrush,OldBrush;

int i;

 

GetClientRect(hWndMain,&crt);

hdc=GetDC(hWndMain);

 

if (hBit==NULL) {

    hBit=CreateCompatibleBitmap(hdc,crt.right,crt.bottom);

}

hMemDC=CreateCompatibleDC(hdc);

OldBit=(HBITMAP)SelectObject(hMemDC,hBit);

 

FillRect(hMemDC,&crt,GetSysColorBrush(COLOR_WINDOW));

 

if (x <= R || x >= crt.right-R) {

    xi*=-1;

}

if (y <= R || y >= crt.bottom-R) {

    yi*=-1;

}

x+=xi;

y+=yi;

 

for (i=0;i<crt.right;i+=10) {

    MoveToEx(hMemDC,i,0,NULL);

    LineTo(hMemDC,i,crt.bottom);

}

 

for (i=0;i<crt.bottom;i+=10) {

    MoveToEx(hMemDC,0,i,NULL);

    LineTo(hMemDC,crt.right,i);

}

 

hPen=CreatePen(PS_INSIDEFRAME,5,RGB(255,0,0));

OldPen=(HPEN)SelectObject(hMemDC,hPen);

hBrush=CreateSolidBrush(RGB(0,0,255));

OldBrush=(HBRUSH)SelectObject(hMemDC,hBrush);

Ellipse(hMemDC,x-R,y-R,x+R,y+R);

DeleteObject(SelectObject(hMemDC,OldPen));

DeleteObject(SelectObject(hMemDC,OldBrush));

 

SelectObject(hMemDC,OldBit);

DeleteDC(hMemDC);

ReleaseDC(hWndMain,hdc);

InvalidateRect(hWndMain,NULL,FALSE);

}

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

HDC hdc,hMemDC;

PAINTSTRUCT ps;

HBITMAP OldBit;

RECT crt;

 

switch(iMessage) {

case WM_CREATE:

    x=50;

    y=50;

    xi=4;

    yi=5;

    SetTimer(hWnd,1,25,NULL);

    return 0;

case WM_TIMER:

    OnTimer();

   return 0;

case WM_PAINT:

    hdc=BeginPaint(hWnd, &ps);

    GetClientRect(hWnd,&crt);

    hMemDC=CreateCompatibleDC(hdc);

    OldBit=(HBITMAP)SelectObject(hMemDC, hBit);

    BitBlt(hdc,0,0,crt.right,crt.bottom,hMemDC,0,0,SRCCOPY);

    SelectObject(hMemDC, OldBit);

   DeleteDC(hMemDC);

    EndPaint(hWnd, &ps);

    return 0;

case WM_DESTROY:

    if (hBit) {

       DeleteObject(hBit);

    }

    PostQuitMessage(0);

    KillTimer(hWnd,1);

    return 0;

}

return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

전역 비트맵 핸들 hBit가 선언되어 있으며 이 비트맵은 작업영역과 동일한 크기대로 생성된다. OnTimer에서 메모리 DC를 생성하고 이 DC에 비트맵을 선택한 후 메모리 DC에 그림을 출력하면 모든 출력이 비트맵에 작성될 것이다. Bounce 예제의 WM_PAINT에 있던 그리기 코드들이 모두 OnTimer로 이동되었다. OnTimer는 비트맵에 그림을 그린 후 InvalidateRect 함수를 호출하여 작업영역을 무효화하기만 한다. 이때 비트맵으로 화면을 완전히 덮을 수 있으므로 작업 영역은 지울 필요가 없으며 마지막 인수는 FALSE로 주어야 한다.

이 예제에서 OnTimer 함수는 내부 버퍼에 미리 그림을 그려 두는 작업을 하는데 이 함수가 더블 버퍼링의 핵심이다. OnTimer의 직접적인 결과물은 hBit에 그려진 그림뿐이며 이 비트맵에 그림을 그리는 과정은 아무래도 상관없다. 모두 지운 후 그리든, 엎어서 그리든 어차피 사용자에게는 보이지 않는다.

이 코드에서 흔히 오해하기 쉬운 것이 있는데 메모리 비트맵인 hBit와 메모리 DC인 hMemDC와의 관계이다. GDI 출력 함수들은 반드시 DC 핸들을 요구하며 비트맵에 출력하기 위해서는 이 비트맵을 선택하고 있는 메모리 DC의 핸들이 필요하다. 그래서 화면 DC와 호환되는(=비트맵과 호환되는) hMemDC를 생성하고 여기에 비트맵을 선택한 후 출력했다. 이 DC는 어디까지나 비트맵 출력을 위한 임시 DC이므로 비트맵을 다 작성하고 난 다음에는 해제되어야 한다.

더블 버퍼링에서 내부 버퍼라고 칭하는 것은 비트맵이지 메모리 DC가 아니다. 메모리 DC는 비트맵을 선택하기 위해 잠시만 사용되는 DC일 뿐인데 알다시피 비트맵을 선택할 수 있는 DC는 메모리 DC밖에 없기 때문이다. 그래서 전역으로 저장해야 할 대상은 hBit 비트맵이지 hMemDC가 아니다.

WM_PAINT에서는 OnTimer가 작성해 놓은 비트맵을 화면으로 전송하기만 한다. 즉, 이미 그려져 있는 그림(내부 버퍼)을 화면(외부 버퍼)으로 복사만 하는 것이다. 실행 결과는 다음과 같다. 지면으로 보기에는 결과가 동일하지만 실제로 실행해 보면 깜박임을 전혀 느낄 수 없을 것이다. 애니메이션이 아주 부드럽게 실행된다.

이 경우도 OnTimer에서 FillRect 함수를 호출하여 이전 그림을 지우기는 하는데 내부 버퍼에서 일어나는 일이기 때문에 화면을 지운 상태는 사용자 눈에 보이지 않으며 따라서 깜박임이 전혀 없는 것이다. 이런 방식대로라면 여러 개의 공을 한꺼번에 움직이더라도 전혀 무리가 없다. Bounce3 예제는 한꺼번에 10개의 공을 움직인다.

 

#define R 20

int x[10],y[10];

int xi[10],yi[10];

HBITMAP hBit;

void OnTimer()

{

RECT crt;

HDC hdc,hMemDC;

HBITMAP OldBit;

HPEN hPen,OldPen;

HBRUSH hBrush,OldBrush;

int i,ball;

 

GetClientRect(hWndMain,&crt);

hdc=GetDC(hWndMain);

 

if (hBit==NULL) {

    hBit=CreateCompatibleBitmap(hdc,crt.right,crt.bottom);

}

hMemDC=CreateCompatibleDC(hdc);

OldBit=(HBITMAP)SelectObject(hMemDC,hBit);

 

FillRect(hMemDC,&crt,GetSysColorBrush(COLOR_WINDOW));

for (i=0;i<crt.right;i+=10) {

    MoveToEx(hMemDC,i,0,NULL);

    LineTo(hMemDC,i,crt.bottom);

}

 

for (i=0;i<crt.bottom;i+=10) {

    MoveToEx(hMemDC,0,i,NULL);

    LineTo(hMemDC,crt.right,i);

}

 

hPen=CreatePen(PS_INSIDEFRAME,5,RGB(255,0,0));

OldPen=(HPEN)SelectObject(hMemDC,hPen);

hBrush=CreateSolidBrush(RGB(0,0,255));

OldBrush=(HBRUSH)SelectObject(hMemDC,hBrush);

 

for (ball=0;ball<10;ball++) {

    if (x[ball] <= R || x[ball] >= crt.right-R) {

       xi[ball]*=-1;

    }

    if (y[ball] <= R || y[ball] >= crt.bottom-R) {

       yi[ball]*=-1;

    }

    x[ball]+=xi[ball];

    y[ball]+=yi[ball];

 

    Ellipse(hMemDC,x[ball]-R,y[ball]-R,x[ball]+R,y[ball]+R);

}

 

 

DeleteObject(SelectObject(hMemDC,OldPen));

DeleteObject(SelectObject(hMemDC,OldBrush));

 

SelectObject(hMemDC,OldBit);

DeleteDC(hMemDC);

ReleaseDC(hWndMain,hdc);

InvalidateRect(hWndMain,NULL,FALSE);

}

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

HDC hdc,hMemDC;

PAINTSTRUCT ps;

HBITMAP OldBit;

RECT crt;

int ball;

 

switch(iMessage) {

case WM_CREATE:

    for (ball=0;ball<10;ball++) {

       x[ball]=50;

       y[ball]=50;

    }

    xi[0]=4;yi[0]=5;

    xi[1]=5;yi[1]=4;

    xi[2]=3;yi[2]=4;

    xi[3]=8;yi[3]=3;

    xi[4]=3;yi[4]=8;

    xi[5]=2;yi[5]=1;

    xi[6]=10;yi[6]=12;

    xi[7]=12;yi[7]=16;

    xi[8]=3;yi[8]=3;

    xi[9]=6;yi[9]=7;

    SetTimer(hWnd,1,25,NULL);

    return 0;

case WM_TIMER:

    OnTimer();

    return 0;

case WM_PAINT:

    hdc=BeginPaint(hWnd, &ps);

    GetClientRect(hWnd,&crt);

    hMemDC=CreateCompatibleDC(hdc);

    OldBit=(HBITMAP)SelectObject(hMemDC, hBit);

    BitBlt(hdc,0,0,crt.right,crt.bottom,hMemDC,0,0,SRCCOPY);

    SelectObject(hMemDC, OldBit);

    DeleteDC(hMemDC);

    EndPaint(hWnd, &ps);

    return 0;

case WM_DESTROY:

    if (hBit) {

       DeleteObject(hBit);

    }

    PostQuitMessage(0);

    KillTimer(hWnd,1);

    return 0;

}

return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

공의 좌표를 기억하는 x,y와 좌표의 증분값인 ix,iy를 크기 10의 배열로 정의했으며 OnTimer에서 10번 루프를 돌면서 공 10개를 한꺼번에 이동시킨다. 각 공의 이동 증분을 다르게 주어 이동 속도를 다양하게 처리해 보았다. 직접 실행해 보면 알겠지만 한꺼번에 10개의 물체가 복잡하게 움직이더라도 전혀 깜박거리지 않으며 속도가 심하게 느려지는 것도 아니다.

더블 버퍼링의 단점이라면 일단 내부 버퍼에 그림을 그린 후 다시 외부 버퍼로 전송하기 때문에 전체적인 속도가 느려질 수 있다는 점이다. 이때 1초에 몇번씩 전송할 수 있는가를 프레임 레이트(Frame Rate)라고 하는데 내부 버퍼에 그림을 준비하는 과정이 복잡할수록 프레임 수가 떨어진다. 다행히 보통 사람의 눈은 1초에 16번 이상의 변화를 감지하지 못하기 때문에 16프레임 이상만 되면 부드러운 움직임을 구현할 수 있다. 물론 그보다 프레임 수가 더 높으면 애니메이션의 품질은 더욱 높아질 것이다.

반응형
반응형

http://www.soen.kr/

깜박임 문제

윈도우에 무엇인가를 반복적으로 출력하려면 깜박임(Flickering)이 발생한다. 특히 반복적으로 그림이 교체되는 애니메이션이나 게임같은 경우 깜박임이 무척 심한데 이런 깜박임은 눈을 피로하게 만들기 때문에 프로그램의 질을 현격하게 떨어뜨린다. 이런 프로그램을 오래 쓰다 보면 심지어 머리까지 아파올 지경이다. 그렇다면 왜 깜박임이 발생하는지 원인을 분석해 보고 그 해결책을 모색해 보도록 하자.

다음 Bounce 예제는 배경 화면에 바둑판 모양의 무늬를 그려 두고 이 무늬 위에서 공을 이동시킨다. 공은 윈도우의 벽에 부딪치면 입사각과 같은 각도로 반사되어 사각의 폐쇄된 공간에서 반복적인 반사 운동을 한다. 게임이든 애니메이션이든 화면에서 무엇인가 움직이는 프로그램이라고 가정하도록 하자. 문제를 정형화하기 위해 메인 윈도우의 크기는 640*350의 고정 크기를 갖도록 했는데 가변 크기라도 해결 방법은 비슷하게 적용할 수 있다.

 

#define R 20

int x,y;

int xi,yi;

void OnTimer()

{

RECT crt;

 

GetClientRect(hWndMain,&crt);

if (x <= R || x >= crt.right-R) {

    xi*=-1;

}

if (y <= R || y >= crt.bottom-R) {

    yi*=-1;

}

x+=xi;

y+=yi;

 

InvalidateRect(hWndMain,NULL,TRUE);

}

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

HDC hdc;

PAINTSTRUCT ps;

HPEN hPen,OldPen;

HBRUSH hBrush,OldBrush;

RECT crt;

int i;

 

switch(iMessage) {

case WM_CREATE:

    x=50;

    y=50;

    xi=4;

    yi=5;

    SetTimer(hWnd,1,25,NULL);

    return 0;

case WM_TIMER:

    OnTimer();

    return 0;

case WM_PAINT:

    hdc=BeginPaint(hWnd, &ps);

    GetClientRect(hWnd,&crt);

    for (i=0;i<crt.right;i+=10) {

       MoveToEx(hdc,i,0,NULL);

       LineTo(hdc,i,crt.bottom);

    }

 

    for (i=0;i<crt.bottom;i+=10) {

       MoveToEx(hdc,0,i,NULL);

       LineTo(hdc,crt.right,i);

    }

 

    hPen=CreatePen(PS_INSIDEFRAME,5,RGB(255,0,0));

    OldPen=(HPEN)SelectObject(hdc,hPen);

    hBrush=CreateSolidBrush(RGB(0,0,255));

    OldBrush=(HBRUSH)SelectObject(hdc,hBrush);

    Ellipse(hdc,x-R,y-R,x+R,y+R);

    DeleteObject(SelectObject(hdc,OldPen));

    DeleteObject(SelectObject(hdc,OldBrush));

    EndPaint(hWnd, &ps);

    return 0;

case WM_DESTROY:

    PostQuitMessage(0);

    KillTimer(hWnd,1);

    return 0;

}

return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

코드 자체는 무척 간단하다. 네 개의 전역 변수와 한 개의 매크로 상수가 정의되어 있는데 x,y는 공의 현재 좌표이며 R은 공의 반지름, xi, yi는 각각 공의 수평, 수직 이동 증분이다.  공의 반지름은 20으로 정의되어 있으며 최초 공은 (50,50)에 위치하여 매 0.025초마다 x쪽으로 4픽셀씩 움직이며 y쪽으로 5픽셀씩 움직인다.

WM_CREATE에서 타이머를 설치하고 OnTimer에서 x,y좌표를 xi, yi만큼 증가시키되 벽에 부딪치면 xi, yi의 부호를 바꾸어준다. 즉 아래로 내려가다가 바닥에 닿으면 다시 위로 올라가며 오른쪽 벽에 부딪히면 왼쪽으로 이동한다. OnPaint에서는 바둑판의 격자 무늬를 출력하고 x,y좌표에 공을 출력하였다. 실행 모습은 다음과 같다.

실제로 실행해 보면 공이 움직이기는 하지만 화면 깜박임이 아주 심하게 느껴질 것이며 컴퓨터가 느리면 느릴수록 깜박임이 더욱 심해진다. 컴퓨터가 아주 빠르다면 깜박임의 정도가 덜하겠지만 그렇다고 해서 깜박임이 없어지는 것은 아니며 다만 깜박임의 주파수만 높아질 뿐이다. 그렇다면 왜 이런 깜박임이 생기는 것일까? 그 이유는 InvalidateRect 함수의 마지막 인수에 있는데 이 값이 TRUE이면 화면 전체를 다시 지운 후 그리기 때문이다.

화면을 다시 지우면 WM_ERASEBKGND 메시지가 발생하며 이 메시지에서 윈도우 클래스에 등록된 배경 브러시로 작업영역 전체를 완전히 지운다. 그리고 WM_PAINT에서 깨끗하게 지워진 작업영역에 격자와 공을 다시 그리기 때문에 완전히 지워진 상황과 그림이 그려진 상황이 반복적으로 눈에 보이게 되므로 깜박임이 느껴지는 것이다.

격자가 촘촘히 그려진 화면과 흰색으로 깨끗하게 지워진 화면이 계속 교체되므로 화면이 떨리는 것처럼 느껴진다. 이때 깜박임의 정도는 화면이 지워진 후 얼마나 빨리 다시 그리기를 하는가, 즉 빈 화면인 상태가 얼마나 오래 가는가에 따라 달라진다. 격자 무늬와 공을 최대한 빨리 그리면 깜박임의 정도가 덜해지기는 하지만 그래도 흰 화면이 아예 없어지지 않는 한 깜박임이 없어지지는 않는다.

깜박임의 원인이 화면을 지우는 것 때문이라면 화면을 지우지 않음으로써 깜박임을 없앨 수 있을 것이다. 과연 그런지 InvalidateRect 함수의 마지막 인수를 FALSE로 바꾼 후 테스트해 보자. 또는 WM_ERSEBKGND 메시지를 막아 버리든가 아니면 윈도우 클래스의 배경 브러시를 NULL로 지정해 주어도 된다. 결과는 다음과 같다.

실제로 실행해 보면 과연 화면은 전혀 깜박이지 않는다. 원래 그려져 있던 그림을 지우지 않기 때문에 빈 화면이 눈에 보이지 않으므로 깜박임이 없는 것이다. 그렇지만 지금 이 결과가 원하는 바는 아니다. 공이 움직이기는 하지만 잔상이 지워지지 않기 때문에 원래 그려져 있던 공이 그 자리에 계속 남아있으며 장면 전환이 제대로 되지 않았다.

결국 애니메이션을 제대로 처리하려면 원래 그려져 있던 그림을 지우고 새 그림을 다시 그려야 한다. 그래서 InvalidateRect 함수의 마지막 인수를 TRUE로 해 주거나 아니면 따로 잔상을 지워 주는 코드를 작성해야 한다. 애니메이션을 제대로 하면서 깜박임을 없애려면 여러 가지 방법을 동원할 수 있다.

우선 가장 쉬운 방법은 무효영역을 최소화하여 깜박임을 거의 느낄 수 없도록 만드는 것이다. 위 예제의 경우 움직이는 물체는 공 뿐이고 이 공은 최대 5픽셀 이상 움직이지 않으므로 현재 공의 위치 x,y에서 반지름 R과 최대 이동거리 5만큼의 영역만 무효화시킨다. 그러면 공 주변만 다시 그려지고 나머지는 그대로 있게 되므로 그리는 속도가 빨라지고 깜박임을 거의 느낄 수 없게 된다. 이 방법은 속도가 아주 빠른 장점이 있으며 실제로 거의 깜박임을 느낄 수 없을 정도로 효율이 좋기는 하다. 하지만 애니메이션 영역이 좁아야 한다는 제약이 있어 일반적인 해법이라 할 수 없다.

두번째 방법은 원래 그림을 지우지는 않되 새로 그려지는 그림으로 덮어 쓰는 것이다. 그러면 적어도 빈 화면이 보이지는 않기 때문에 깜박임은 눈에 보이지 않는다. 예를 들어 흰 바탕에 "ABC" 문자열이 출력되어 있는 상황에서 "de"로 교체한다고 해 보자. 이때 이전 문자를 지우지 않고 "de"만 출력하면 "deC"라고 출력될 것이다. 이때 "de" 뒤에 공백을 넣어 "de    "를 출력하면 이전 문자를 완전히 깔끔하게 덮어 버릴 수 있다. 이 방법은 배경과 그려지는 그림이 단순할 때만 적용할 수 있으며 위 예제는 격자가 있기 때문에 적용하기 어렵다.

이외에 깜박임을 최소화할 수 있는 여러 가지 방법들이 있는데 프로그램의 상황에 따라 적용할 수 있는 기법에 제약이 아주 많다. 움직이는 물체가 많거나 서로 겹치지 않도록 해야 한다면 이런 간단한 방법들을 쓰기는 어렵다. 더구나 게임같은 복잡한 프로그램은 물체가 스스로 애니메이션까지 되어야 하므로 보통의 방법으로는 깜박임을 제거하기 어렵다. 깜박임을 없애기 어려운 근본적인 이유는 원래 그림을 지워야만 새로운 그림을 출력할 수 있기 때문이다.

반응형
반응형

http://rintiantta.blog.me/40114652444


Console 프로젝트를 만들어줍니다 ㅎㅎ 

 


 

우선 ㅇㅅㅇ, int 로 리스트를 만들어주었습니다. ㅎㅎ


 

만약 여기서 ㅇㅅㅇ, 30 이상인 녀석들만 뽑겠다면 아래와 같이 해야겠지요 ?
 

 

이렇게 걸리적 거리게 무엇을 하는 것을 막고자 ㅇㅅㅇ

무언가를 더 짧고 명료하게 쓰고자, Linq 라는 녀석이 나온 것이랍니다. ㅎㅎ

 

SQL 과 비슷한 문장입니다.

 

우선 살펴보죠 ㅇㅅㅇ, from A in B 는 foreach 와 비슷한 녀석입니다.

반복을 돌릴 것인데, B 에 있는 녀석을 A 에 넣고 그 녀석을 돌릴 거라는 이야기지요 ㅎㅎ

where 은 다음에 보구요 ㅇㅅㅇ, select 는 그렇게 반복이 된 것중에서 우리가 원하는 것을 뺀다는 얘기입니다.

 

아래의 경우 list 에 있는 숫자들이 하나하나 item 에 들어가서 30이 넘는지 확인을 하고

하나하나 item 으로 나가면서 IEnumerator 가 만들어 진답니다.

 

 

결과적으로 ㅇㅅㅇ, numQuery 라는 곳에는 list 중 30이 넘는 녀석들이 다시 배열로 만들어지겠죠.

그럼 아래와 같이 foreach 를 사용해서 출력을 해보겠습니다.
 

 

하고나니 느낀건데.. 30 이하가 애초부터 없었다능 ㅇㅅㅇ...

어찌하였거나, Linq 라고 하는 것은 foreach 와 if 등을 사용해 모두 구현이 가능하지만

Linq 를 사용하면 훨씬 짧게 구현이 가능하답니다 >_<

 

또한 XML 과 SQL 과 연동되어 더욱더 강력한 성능을 발휘하는 Linq..!

차근차근 알아보도록 합시다 ㅎㅎ
 

 
끝 ~


반응형
반응형
https://msdn.microsoft.com/ko-kr/windows/apps/b2s063f7

XML 문서 주석(C# 프로그래밍 가이드)

Visual C#에서는 주석이 참조하는 코드 블록 바로 앞의 소스 코드의 특별 주석 필드(세 개의 슬래시로 표시)에 XML 요소를 포함하여 코드에 대한 문서를 만들 수 있습니다. 예를 들면 다음과 같습니다.

/// <summary>
///  This class performs an important function.
/// </summary>
public class MyClass{}

/doc 옵션을 사용하여 컴파일하는 경우 컴파일러는 소스 코드에서 모든 XML 태그를 검색하고 XML 문서 파일을 만듭니다. 컴파일러에서 생성한 파일을 기반으로 최종 문서를 만들려면 사용자 지정 도구를 만들거나 Sandcastle과 같은 도구를 사용하면 됩니다.

XML 요소를 참조하려면(예를 들어, 함수가 XML 문서 주석에서 설명하려는 특정 XML 요소를 처리하는 경우) 표준 인용 메커니즘(&lt;&gt;)을 사용할 수 있습니다. 코드 참조(cref) 요소에서 제네릭 식별자를 참조하려면 이스케이프 문자(예: cref=”List&lt;T>”) 또는 괄호(cref=”List{T}”)를 사용할 수 있습니다. 특별한 경우로, 컴파일러는 제네릭 식별자를 참조할 때 문서 주석을 더 쉽게 작성할 수 있도록 괄호를 꺾쇠 괄호로 구문 분석합니다.

참고참고

XML 문서 주석은 메타데이터가 아닙니다. 이러한 주석은 컴파일된 어셈블리에 포함되지 않으므로 리플렉션을 통해 액세스할 수 없습니다.


반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

GetHashCode()  (0) 2015.11.11
C# Linq 1강 - Linq 맛보기  (0) 2015.09.10
[C#] 해시테이블 vs 딕셔너리 ( hashtable vs. dictionary ) |  (0) 2015.09.02
C# queue  (0) 2015.09.02
c# Stack  (0) 2015.09.02
반응형

http://hongjinhyeon.tistory.com/87


C#에서는 KEY 와 VALUE를 사용해서 자료를 저장하는 타입이 2가지가 있습니다.

해시테이블과 딕셔너리인데 사용법은 거의 동일하지만 내부적으로 처리하는 기술이 다릅니다.

이 두가지 타입의 기본적인 사용법과 장단점에 대해서 알아보겠습니다.


1.해시테이블 ( Hashtable)


01.//생성
02.Hashtable hashtable = new Hashtable();
03. 
04.//자료 추가 ( 박싱이 일어남)
05.hashtable.Add("Data1"new HongsClass() { Name = "홍진현1", intCount = 1 });
06.hashtable.Add("Data2"new HongsClass() { Name = "홍진현2", intCount = 2 });
07. 
08.//자료 검색
09.if (hashtable.ContainsKey("Data1").Equals(true))
10.{
11.HongsClass temp = hashtable["Data1"as HongsClass; (언박싱 처리)
12.Console.WriteLine(temp.Name);
13.}
14. 
15.//Loop 전체 순회출력
16.foreach (string NowKey in hashtable.Keys)
17.{
18.HongsClass temp = hashtable[NowKey] as HongsClass;
19.Console.WriteLine(temp.Name);
20. 
21.}
22. 
23.//결과  OUTPUT
24.//홍진현1
25.//홍진현1
26.//홍진현2
27. 
28.<p></p>


[해시테이블의 특징]


1.Non-Generic

2.Key 와 Value 모두 Object를 입력받는다.

3.박싱/언박싱(Boxing/Un-Boxing) (참고: http://hongjinhyeon.tistory.com/90을 사용한다. 



즉, 제네릭을 이용하지 않고 Object를 사용하기 때문에 모든 데이터 타입을 다 받고 처리 할 수 있는 장점이 있지만

자료의 입력에 내부적으로 박싱이 일어나고 사용하는 곳에서도 다시 언박싱을 해줘야 사용이 가능합니다.



2.딕셔너리 ( Dictionary )


01.//생성- 제네릭 기반
02.Dictionary<string, HongsClass> dictionary = new Dictionary<string, HongsClass>();
03. 
04.//자료추가
05.dictionary.Add("Data1"new HongsClass() { Name = "홍진현1", intCount = 1 });
06.dictionary.Add("Data2"new HongsClass() { Name = "홍진현2", intCount = 2 });
07. 
08.//자료검색
09.if (dictionary.ContainsKey("Data1").Equals(true))
10.{
11.Console.WriteLine(dictionary["Data1"].Name);
12.}
13. 
14.//Loop 전체 순회출력
15.foreach (HongsClass NowData in dictionary.Values)
16.{
17.Console.WriteLine(NowData.Name);
18.}
19. 
20.//결과
21.//홍진현1
22.//홍진현1
23.//홍진현2

[딕셔너리의 특징]



1.Generic
2.Key와 Value 모두 Strong Type을 입력받는다.(선언시 타입을 입력해줘야함)

3.박싱/언박싱이 일어나지 않는다.



+딕셔너리는 선언시에 사용할 타입을 미리 설정하기 때문에 입력시나 출력시에도 박싱/언박싱이 일어나지 않습니다.

따라서 입력과 사용에 용이하며, 외부에서도 이 타입을 사용할 때도 타입이 정의되어 있으니 다른 타입으로 형변환을

시도하다가 실패할 염려가 없습니다.



3.결론


+두가지 타입이 사용법은 비슷하지만 내부적인 처리와 수용하는 타입의 형태가 다르므로 필요에 따라서 선택을 해야합니다.

고정적으로 하나의 타입만 입력받을 시에는 딕셔너리를 사용하며, Value에 일정한 형식이 없고 여러 형태를 저장하려면

해시테이블을 사용해야합니다.



반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

C# Linq 1강 - Linq 맛보기  (0) 2015.09.10
c# XML 문서 주석(C# 프로그래밍 가이드) /// <summary>  (0) 2015.09.02
C# queue  (0) 2015.09.02
c# Stack  (0) 2015.09.02
C# - Yield  (0) 2015.09.02
반응형

http://rintiantta.blog.me/40115267806



프로젝트를 만듭니다. ㅎㅎ 
 


 

똑같습니다. Queue 를 만드시면됩니다. ㅇㅅㅇ

 

 

종이컵 리필할때는 Enqueue() 를 사용하시면 댑니다 ~_~

 

 

반대로, 빼낼때는 Dequeue() 를 쓰시면 됩니다. ㅎㅎ

 

 

결과입니다. ~_~

 

 

아래와 같이 foreach 로 하시면요 ㅇㅅㅇ...

 

 

내용물을 볼 수는 있어도 실질적으로 종이컵이 빠지는 것이 아닙니다.

(저번 Stack 도 마찬가지 ㅇㅅㅇ..)



 

아래와 같이 Dequeue() 를 쓰시면 내용물도 뺄 수 있답니다. ㅎㅎ

(뺀다는 것이 get 을 의미하는 것이 아니에요. 해당 컬렉션에서 실질적으로 없애버린다는 겁니다.)

 

 

결과입니다. >ㅅ< 



 

역시나, Stack 과 마찬가지로 내용물이 없는데 계속 빼려고 하면요.

 

 

익셉션이 뜹니다. ㅎㅎ...

 

 
그럼 오늘 강의도 끝 ~_~


반응형
반응형

http://rintiantta.blog.me/40115267727




자, 배고프지만 시작합시다 ㅎㅎ...!

프로젝트를 만듭니다. ㅇㅅㅇ ... ! 
 


 

오늘부터 중급강의에서 볼 내용은 Stack 입니다.

Stack 은 아래와 같이 만듭니다. ㅇㅅㅇ ...!

스택에란 스택스택 쌓아두는 곳이지요. (스테이크 쌓듯이 ㅎㅎ)



 

오늘 이대를 다녀왔는데 거기 떡갈비 디게 맛있어요 ㅎㅎ...

떡갈비를 아주머니가 만드시면 타지 않게 앞에 올려두죠 ?

 

   ㅁ

   ㅁ

   ㅁ

   ㅁ

-------

위의 떡갈비(...) 4개 중에서 어떤게 가장 먼저 만들어진 걸까요?

"맨 아래꺼요 >ㅆ<"

『넴 ㅎㅎ, 그렇습니다. ㅇㅅㅇ...! 그리고 아주머니가 파실 때는 윗쪽 것부터 훅훅 파시지요 ㅇㅅㅇ... 그게 Stack 입니다 >ㅆ<』

 

아래와 같이 객체를 만들어서, 스택스택 쌓아두시면 됩니다.



 

아래와 같이 출력이 가능하지만요 ㅇㅅㅇ..

이럼 List 와 다를 빠가 없지요 =_= ...

 

 

어쨌건 출력입니다. ㅇㅅㅇ...

"List와 다를바가 있기는 한데요 ... ?"

『역으로 출력되요 ㅇㅅㅇ... 떡갈비 윗쪽 부터 훑는 것이지요 ㅎㅎ』



 

근데 떡갈비를 보기만 하면 아주머니가 화내시겠죠..

팔아서 없애야죠 ㅇㅅㅇ !!!


아래와 같이 Pop 을 사용해서 없애주시면 됩니다. ㅎㅎ

 


 

결과입니다. ㅇㅅㅇ ... !

 

 

스택에서 주의하실 것은 아래와 같이 팔거 없는데

계속 팔라고하면

 

 

익셉션 뜹니다. ~_~

주의해 주세요 ㅎㅎ...



 
그럼 오늘 강의 끝 >ㅅ<

Stack 과 같은 녀석을 잘 쓰지 않지요 ㅇㅅㅇ..

 

근데, 아이폰 공부하신 분은 아시겠지만 하다보면 Push() 와 Pop() 이 나옵니다.

직접적으로 관련은 없어도, 다른 것을 이해하는데 도움을 줄 수 있는 부분입니다. ㅎㅎ


반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

[C#] 해시테이블 vs 딕셔너리 ( hashtable vs. dictionary ) |  (0) 2015.09.02
C# queue  (0) 2015.09.02
C# - Yield  (0) 2015.09.02
C# | DLL 만들고 사용하기  (0) 2015.09.02
리플렉션(refelction) 활용  (0) 2015.09.01
반응형

http://rintiantta.blog.me/40113858529



Yield 라는 키워드는 Break 와 Return 과 함께 쓰인답니다.

음.. Break 는 자주 활용이 되지는 않는 것 같습니다만, 강의이니 뭐 알아봐야죠 ㅎ..! 

 

간단하게, Yield 라는 키워드는 일반적인 것을 IEnumerable 로 변환을 시켜줍니다..

뭔소린지, 해보면서 봅시다..!

 

우선 프로젝트를 만듭니다. 이름은 EnumYieldLesson 입니다..!

 

 

원래, 강의중에 클래스를 따로 만드는 타입이잖습니까..

일반 교재에는 다 합치지만.. 실제로 안 그러니... 연습도 되시라구 하는데...

오늘건 나누면 조금 길어지는 관계로 합쳤습니다... 죄송합니다 =_=...

 

어찌하였거나, YieldTest 라는 이름의 클래스를 만들어주었습니다 ㅇㅅㅇ..!

 

 

그리고 아래와 같이 days 라는 이름으로 만들어주었습니다.

참고로, MSDN 에 있는 내용인데요. 음... 그냥 Yield Return 과 GetEnumerator 에 대한 설명밖에 없길래

추가하고 합쳐보았습니다. ㅎㅎ.... (MSDN은 저작권이.. 교육용으로는 안 걸리지...)

 

 

다음과 같은 IEnumerator 로 리턴하는 GetEnumerator라는 메소드를 만듭니다.

using 하라는 말이 뜨니 using 해줍시다.

 

참고로, 이름을 바꿔서 예제를 따라해보는 분들이 계실텐데요. (제가 그런 타입이라..)

GetEnumerator 라는 이름은 고정하셔야 합니다. 잠시후 뒤에서 언급할게요.

 

 

그리고 아래와 같이 메소드를 작성했는데요.

일단 리턴값이 IEnumerator 고, 리턴시 yield 라는 것을 쓴 것을 보실 수 있답니다.

리턴이 분명 string 으로 될텐데, yield 라는 키워드를 붙임으로써, IEnumerable 로 바뀌게 됩니다.

"어라... 리턴이 IEnumerator 인테.. 왜... IEnumerable.. ㅇㅅㅇ ... ?"

『잠시후 봅시다...ㅎ』

 

 

근데 왜 이름이 고정이냐면요 ㅇㅅㅇ

원래 IEnumerable 인터페이스의 상속을 받아서 구현해야합니다. ㅇㅅㅇ

 

 

인터페이스 구현버튼을 누르시면 아래와 같이 GetEnumerator 라는 메소드가 구현되는데요.

원래는 이렇게 해서 해야하지만... region 때문에 길이가 길어서 그냥 하고 있습니다...

 

 

어찌하였거나, 이어서 메소드를 만드는데요.

OtherGetEnum() 이라고 만들었습니다. 내부의 for 문을 foreach도, for 도 된다는 뜻으로 해본거구요.

 

주의하실것은.. IEnumerable 로 리턴한다는 것입니다...

"ㅇㅅㅇ...."

 

 

자, 그럼 이제 만들었으니, 사용해봅시다~!

아래와 같이 인스턴스를 만들었답니다.

 

 

그리고 아래와 같이 실행이가능합니다.

그냥 꼽으시면, foreach 에서 알아서 "아, GetEnumerator() 실행하라는 거구나."

하고 GetEnumerator 를 실행합니다.

 

근데 문제가 있습니다. foreach 에는 IEnumerator 값을 꼽을 수 없습니다.

따라서, 내부에서 한번 더 변형이 일어나서 IEnumerable 로 바뀐답니다.

 

 

실행 결과입니다. 간단하죠 ?

 

 

이어서, 아래와 같이 우리가 만든 녀석을 써보았습니다.

위와 실행 결과가 같답니다 ㅎㅎ

 

 

또한 아래와 같이 따로 뺀다음 꼽을 수도 있는 것을 생각하시기 바랍니다.

"오오, 그럼 IEnumerator 로 GetEnumerator 뽑아서 꼽아도 되는거 아닌가요?"

『해보시길 바랍니다. 에러날겁니다. ㅇㅅㅇ... IEnumerator 는 꼽을 수 없다구요 ㅎ...』

 

 

이어서, yield break; 를 보도록 합시다.

아래와 같이 메소드를 수정했는데요.

 

yield break; 는 yield return 을 끝내라는 말입니다.

즉, 지금 한데까지만 IEnumerable 로 바꾸라는 이야기지요.

 

 

그래서 아래와 같이 것을 실행하시면

 

 

이렇게 됩니다. yield return 이 중간에 끊긴 것을 확인하실 수 있습니다.

 

 

 자... 그럼 마지막으로 무언가 하나를 또 보기위해서 AnotherGetEnum 을 만듭니다.

『...복사해서.. AnOtherGetEnum 이 되었다는 설이..』.... 죄송합니다.

 

 

이어서, 다음과 같이 써주었습니다.

"ㅇㅅㅇ ?!"

실행결과를 보시는 편이 이해가 빠를 것이라고 생각됩니다 ㅎㅎ....

 

 

아래와 같이 메인에 써주었어요 ㅇㅅㅇ..!

 

 

자, 출력결과가 보이시나요 ㅇㅅㅇ ?

하나, 하나, 하나 출력되었습니다.

 

이와같이 yield 키워드는 IEnum 계열과 foreach 와 함께쓰이는 녀석입니다.

 

 

그럼 오늘 강의 끝 ~

오늘건 약간 길었지요 ㅇㅅㅇ....?

 

음.. 아직 베이직에서 아직 인터페이스도 안했는데.... 중급강의에서 나오니 조금 그렇지만..

아직 보신 분이 많지 않으니 괜찮을듯 ~ ㅎㅎ;; 


반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

C# queue  (0) 2015.09.02
c# Stack  (0) 2015.09.02
C# | DLL 만들고 사용하기  (0) 2015.09.02
리플렉션(refelction) 활용  (0) 2015.09.01
열거형 GetEnumerator(), IEnumerable, IEnumerator 정의  (0) 2015.09.01
반응형

http://wwwi.tistory.com/316


C#에서 DLL만들고 사용하기

개발환경은 Visual C# 2010 Express에서의 예제이다.
이 내용은 Visual Studio 2010에서 해도 문제가 없다.

메뉴 표시는 대신 한글이여서 조금은 이해하기 편할 것 같다.


먼저 DLL을 사용할 프로젝트를 만든다.
먼저 File > New Project를 선택한다.

Console Application을 선택하고 적당한 이름을 입력한 후 OK를 누른다.
여기서 입력한 이름으로 프로젝트가 만들어지고 이 이름의 네임스페이스가 생긴다.

그러면 아래와 같이 자동으로 소스가 조금 만들어진다.


사용할 DLL 프로젝트 만들기

솔루션에서 왼쪽 클릭을 하고 Add > New Project를 선택한다.



Class Library를 선택하고 DLL의 이름을 적고 OK를 누른다.


DLL에 대한 내용을 적어둔다.



using System;

namespace MyCompo
{
    public class Calcurate
    {
        public int Add(int a, int b)
        {
            return (a + b);
        }
    }
}


DLL을 Build한다.


작성된 DLL을 참조하기 위해서는 DLL을 사용하려는 프로젝트의 References(참조)에서 오른쪽 클릭을 하여 Add Reference를 선택한다.


그리고 Projects 탭을 선택하면 지금 작성한 DLL의 프로젝트를 선택하고 OK를 누른다.

정상적으로 Dll이 참조되면 References 밑에 추가한 DLL의 이름이 표시된다.

그리고 아래와 같이 코드를 작성한다.


using System;
// DLL Namespace 추가
using MyCompo;

namespace UseDll
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;
            int c = 0;

            // DLL에 정의된 클래스로 객체 정의하기
            Calcurate cal = new Calcurate();

            // DLL의 메서드 사용하기
            c = cal.Add(a,b);

            Console.WriteLine("{0} + {1} = {2}", a, b, c);
            Console.ReadLine(); 
        }
    }
}

그리고 실행하면 아래와 같은 결과가 표시된다.


반응형
반응형
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=17&MAEULNo=8&no=33805&ref=33729


간단히 말하면 런타임으로 동적으로 다양한 작업을 하고자 할 때 사용합니다.

 

1) 다음과 같이 메타 정보를 런타임에 얻고자 할 경우

 

메타 정보란 응용프로그램의 기능와 상관없는 정보

=> 실제 작업코드가 아닌 정보들...

=> 그 파일에 따라다니는 파일과 관련된 정보들

 

private string FileVersion()

{

    Assembly asm=Assembly.LoadFrom(@"C:\abc.exe");

    AssemblyName name=asm.GetName();

 

    return name.Version.ToString();

}

 

라이브러리를 개발하면서 유지보수 내용을 기록하고 이런 메타 정보를 얻어서 유지보수등에 도움을 받기도 하고...

 

2) 어셈블리의 내용을 알고자 할 때

이벤트, 필드, 메소드 등등

 

Assembly asm = Assembly.Load("Mscorlib.dll");

Type[] types = asm.GetTypes();

foreach(Type t in types)

{

    Console.WriteLine("Type : {0}", t)

}

 

3) 개체의 이름으로 개체의 인스턴스 만들기

=> 융통성 있는 코드를 작성할 수 있습니다.

 

예를 들어

다음과 같이 하면 오직 Form1만 열게 되고

private void OpenForm1()

{

    Form frm = new Form1();

    frm.Show();

}

 

다음과 같이 switch 구문을 사용해도 폼이 추가되면 또 코드를 수정해야 되고

private void OpenForm(string formName)

{

    Form frm = null;

 

    switch(formName)

    {

        case "Form1":

            frm = new Form1;

            break;

        case "Form2":

            frm = new Form2;

            break;

        ....

    }

    frm.Show();

}

 

그러나 리플렉션을 이용하면...

  

private button1_Click(.....)

{

    OpenFormByName(this.textBox1.Text);

}

 

private void OpenFormByName(string formName)

    Assembly am = Assembly.GetEntryAssembly();

    string fullName = asm.ToString() + "." + formName;

 

    // 리플렉션: 이름(문자정보)으로 해당 타입을 알아내는 것

    // => 이름을 주고 '요놈이 누구인가 찾아주세요' 하면 심사숙고해서 (리플렉션) 결과를 찾아주는 것.

    Type t = Type.GetType( fullName );

 

    // 후기 바인딩: 타입을 가지고 인스턴스를 만들고

    object o = Activator.CreateInstance(t);

 

    // 폼을 보여 줌

    Form f = (Form) o;

    f.Show();

}

 

 

또 이렇게 폼을 리플렉션을 이용하여 작업을 하게되면

폼을 독립된 DLL 파일로 개발하고

폼을 업데이트 하는 경우 해당 DLL 파일만 업데이트 하면 되고....

 

4) COM 개체를 후기바인딩으로 불러 사용

 

private void OpenExcel()

{

    // 리플렉션

    Type excel = Type.GetTypeFromProgID("Excel.Microsoft");

 

    // 후기바인딩

    object objExcel = Activator.CreateInstance(excel)

    object[] param = object[1];

    param[0]=true;

    excel.InvokeMember("Visible", BindingFlags.SerProperty, null, objExcel, param);

}

 

이렇듯 런타임에 동적으로(융통성있게) 개체를 가지고 작업을 할 수 있습니다.

 

후기바인딩으로 인스턴스를 만드는데 쓰이는

Activator의 메소드들은 다음과 같습니다.

 

    CreateComInstanceFrom()

    CreateInstanceFrom()

    GetObject()

    CreateInstance()

 

도움말을 한번 찾아보십시오.

이것을 알면 리플렉션을 어떻게 사용할 것인가에 대해 좀더 알 수도 있을 겁니다.

 

5) 런타임으로 스크립트를 작성하고 실행하거나 컴파일을 하거나 새로운 타입을 동적으로 생성하는 등

고급 작업을 할 수 있습니다.

기타 VS.NET의 IDE와 같은 것을 만들 수도 있겠습니다.

이 부분은 나중에 개념 잡으시면서 접해보도록 하십시오.



반응형
반응형


using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace TestE
{

    class User
    {
        public User(string name) { _name = name; }
        public string Name{ getreturn _name;} }
        string _name;
    }


    class DBManager : IEnumerable
    {
        public DBManager()
        {
            _onlineUsers.Add (new User ("tom"));
            _onlineUsers.Add (new User ("Krista"));
            _onlineUsers.Add (new User ("Emma"));
        }

        public IEnumerator GetEnumerator()                //이 함수를 불릴때 원소를 복사하는 방식 => 메모리 측면에서 효율적인 방법은 아님
        {
            return new Enumerator (_onlineUsers);
        }
            
        ArrayList _onlineUsers = new ArrayList();

        class Enumerator : IEnumerator
        {
            const string INVALID_RECORD = "use ss";

            public Enumerator(ArrayList onlineUsers)
            {
                foreach(User user in onlineUsers)
                {
                    this._onlineUsers.Add(user);
                }
                Reset();
            }

            public bool MoveNext()
            {
                return ++_index < _onlineUsers.Count;
            }

            public object Current
            {
                get
                {
                    if (_index == -1
                        throw new InvalidOperationException (INVALID_RECORD);

                    if (_index < _onlineUsers.Count)
                        return _onlineUsers [_index];
                    else
                        return null;
                    
                }
            }

            public void Reset(){ _index = -1; }

            int _index;
            ArrayList _onlineUsers = new ArrayList();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {

            DBManager db = new DBManager ();

            IEnumerator e = db.GetEnumerator ();

            while (e.MoveNext ()) {
                User user = (User)e.Current;
                Console.WriteLine ("{0}", user.Name);
            }


            foreach (User user in db) {
                Console.WriteLine ("{0}", user.Name);
            }

            Console.ReadLine ();
        }
    }


}

반응형
반응형

http://www.gpgstudy.com/forum/viewtopic.php?p=95504&sid=3186560b92d188fe3fab7bef907f0c67

delegate하고 event하고 뭐가 다른지 잘 몰라서 그러는데요. 


class Test
{
    public delegate void Handler(string msg);
 
    public event Handler Event;
    public Handler Delegate;
 
    public void DoIt()
    {
        Event("event");
        Delegate("delegate");
    }
};
 
class Program
{
    static void MyFunc(string caller)
    {
        Console.WriteLine("Message from {0}", caller);
    }
     
    static void Main(string[] args)
    {
        Test test = new Test();
        test.Event += MyFunc;
        test.Delegate += MyFunc;
        test.DoIt();
    }

}

이 코드를 보면 event나 delegate이나 차이가 없어 보이는데 
혹시 제가 모르는 차이점이 있나요?







저도 예전에 차이점이 궁금했었는데요.. 
겉으로 보기에 용법이 같아서 그렇지 event와 delegate는 다른 놈입니다. 

event와 delegate를 
property와 멤버 변수의 관계랑 비슷하다고 생각하시면 됩니다. 

event는 property가 get, set을 갖는 것 처럼 add, remove함수를 가져야 합니다. 
근데 사용하기 쉽게 add, remove를 생략하실수 있습니다. 


public event Handler Event;  // 생략형



그냥 이렇게 하면 아래와 같은 코드가 만들어집니다. 


private Handler _Event; // _Event라는 delegate 변수를 자동으로 만들어냅니다.
public event Handler Event
{
    add
    {
        lock(this) { _Event += value; }
    }
 
    remove
    {
        lock(this) { _Event -= value; }
    }

}


이 Event 이벤트 변수에다가 +=을 써주면 add가 불리고 -=을 써주면 remove가 불리는 겁니다. 
그래서 겉에서 봤을땐 delegate랑 사용법이 똑같이 보이게 됩니다. 

근데 이것도 약간 다른 점이 이벤트 호출시에 Event("event");를 사용하는데 
이건 클래스 안에서만 사용 가능합니다. 클래스 밖에서 test.Event("event"); 하면 에러가 나지요 
생략형으로 쓰면 c#이 자동으로 Event() 호출을 _Event 대리자의 호출로 치환해 줍니다. 
_Event는 내부 변수이므로 밖에서 호출이 불가능합니다. 

add, remove를 명시적으로 쓸 경우에는 Event("event"); 이런식으로 호출을 못합니다(자동으로 대리자 변수를 만들어내지 않기 때문에) 
직접 _Event("event"); 이런식으로 대리자를 호출해 주거나 그에 상응하는 작업을 구현해야 합니다. 

이러한 차이점이 있기 때문에 event는 interface에서 선언해 줄수 있고요.. delegate는 그렇지 못합니다. 

event변수가 넘쳐나는걸 막기 위해 winforms에서는 메시지 이벤트를 add, remove를 직접 구현하고 dictionary에 event를 저장하는 방식을 쓴다고 합니다. 
http://msdn2.micros...ibrary/z4ka55h8.aspx
 

여기 Events 부분에 자세한 설명이 있습니다. 
http://www.yoda.ara...m/csharp/events.html





https://msdn.microsoft.com/ko-kr/library/z4ka55h8.aspx


방법: 사전을 사용하여 이벤트 인스턴스 저장(C# 프로그래밍 가이드)

각 이벤트에 대해 필드를 할당하는 대신 사전을 사용하여 이벤트 인스턴스를 저장하는 방법으로 많은 이벤트를 노출할 때 accessor-declarations를 사용할 수 있습니다. 이 방법은 많은 이벤트를 사용할 수 있지만 대부분의 이벤트가 구현되지 않을 것으로 예상할 때만 유용합니다.

public delegate void EventHandler1(int i);
public delegate void EventHandler2(string s);

public class PropertyEventsSample
{
    private System.Collections.Generic.Dictionary<string, System.Delegate> eventTable;

    public PropertyEventsSample()
    {
        eventTable = new System.Collections.Generic.Dictionary<string, System.Delegate>();
        eventTable.Add("Event1", null);
        eventTable.Add("Event2", null);
    }

    public event EventHandler1 Event1
    {
        add
        {
            lock (eventTable)
            {
                eventTable["Event1"] = (EventHandler1)eventTable["Event1"] + value;
            }
        }
        remove
        {
            lock (eventTable)
            {
                eventTable["Event1"] = (EventHandler1)eventTable["Event1"] - value;
            }
        }
    }

    public event EventHandler2 Event2
    {
        add
        {
            lock (eventTable)
            {
                eventTable["Event2"] = (EventHandler2)eventTable["Event2"] + value;
            }
        }
        remove
        {
            lock (eventTable)
            {
                eventTable["Event2"] = (EventHandler2)eventTable["Event2"] - value;
            }
        }
    }

    internal void RaiseEvent1(int i)
    {
        EventHandler1 handler1;
        if (null != (handler1 = (EventHandler1)eventTable["Event1"]))
        {
            handler1(i);
        }
    }

    internal void RaiseEvent2(string s)
    {
        EventHandler2 handler2;
        if (null != (handler2 = (EventHandler2)eventTable["Event2"]))
        {
            handler2(s);
        }
    }
}

public class TestClass
{
    public static void Delegate1Method(int i)
    {
        System.Console.WriteLine(i);
    }

    public static void Delegate2Method(string s)
    {
        System.Console.WriteLine(s);
    }

    static void Main()
    {
        PropertyEventsSample p = new PropertyEventsSample();

        p.Event1 += new EventHandler1(TestClass.Delegate1Method);
        p.Event1 += new EventHandler1(TestClass.Delegate1Method);
        p.Event1 -= new EventHandler1(TestClass.Delegate1Method);
        p.RaiseEvent1(2);

        p.Event2 += new EventHandler2(TestClass.Delegate2Method);
        p.Event2 += new EventHandler2(TestClass.Delegate2Method);
        p.Event2 -= new EventHandler2(TestClass.Delegate2Method);
        p.RaiseEvent2("TestString");

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    2
    TestString 

*/

반응형
반응형

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace TestE
{
    class DBConnection {

        protected static int NextConnectionNbr = 1;

        protected string connectionName;
        public string ConnectionName{ get { return connectionName; } }

        public DBConnection()
        {
            connectionName = "Database Connection " + DBConnection.NextConnectionNbr++;
        }

    }


    class DBManager
    {
        protected ArrayList activeConnetions;
        public DBManager()
        {
            activeConnetions = new ArrayList ();
            for (int i = 0; i < 6; ++i) {
                activeConnetions.Add (new DBConnection ());

            }
        }

        public delegate void EnumConnectionsCallback(DBConnection connection);            

//delegate 선언, 이 함수 타입과 동일한 함수를  나중에 담을 수 있게 된다

        public void EnumConnections(EnumConnectionsCallback callback)                     
        {
            foreach(DBConnection connection in activeConnetions)
            {
                callback(connection);
            }
        }
    }



    class Program {

        public static void PrintConnections(DBConnection connection)
        {
            Console.WriteLine ("[InstanceDelegate.PrintConnections] {0}", connection.ConnectionName);
        }

        static int Main(string[] args)
        {

            DBManager dbMsanager = new DBManager ();

            DBManager.EnumConnectionsCallback printConnections = new DBManager.EnumConnectionsCallback

//delegate 와 동일한 함수 타입의 함수인  PrintConnections 를 담는다

            dbMsanager.EnumConnections (printConnections);

            Console.ReadLine ();

            return 0;
        }
    }
}




결과


[InstanceDelegate.PrintConnections] Database Connection 1

[InstanceDelegate.PrintConnections] Database Connection 2

[InstanceDelegate.PrintConnections] Database Connection 3

[InstanceDelegate.PrintConnections] Database Connection 4

[InstanceDelegate.PrintConnections] Database Connection 5

[InstanceDelegate.PrintConnections] Database Connection 6



- 참고 inside c# second

반응형

+ Recent posts