별빛의 낙하지 :: 별빛의 낙하지

[C++0x] Move Operator

C++ 2012. 11. 16. 00:20 Posted by byulbit
/*
	Move Constructor( 이동 연산자 )

	c++0x 에는 기존의 대입연산자에 이어서, 이동 연산자도 추가가 되었다.

*/

#include 

class MoveClass
{
public:
	// Default Constructor
	MoveClass() {
		std::cout << "Default Constructor Call" << std::endl;
	}
	
	// Copy Operator
	void operator=(MoveClass& a) {
		std::cout << "Copy Operator Call" << std::endl;
	}

	
	// Move Operator
	void operator=(MoveClass&& a) {
		std::cout << "Move Operator Call" << std::endl;
	}
};


// 값이 복사되는 곳에서는 일반적으로 복사 생성자가 호출된다.
MoveClass foo(MoveClass& a)
{
	MoveClass local;
	return local;		// 값이 복사 되는데, 우선순위가 존재한다.
						// MoveClass(const MoveClass& a) 형태의 복사 생성자가 존재하고,
						// MoveClass(MoveClass&& a) 형태의 이동 생성자가 존재하면
						// 이동 생성자를 호출한다.
}

int main()
{
	MoveClass a;			// Default Constructor Call
	MoveClass b;
	a = b;					// Copy operator Call
	
	a = std::move(b);		// move Operator Call

}

'C++' 카테고리의 다른 글

STL 요소 만들어보기(1)  (0) 2012.11.16
[C++] template을 사용하여 연결리스트 구현  (0) 2012.11.16
[C++0x] Move Constructor  (0) 2012.11.15
[C++] C++ name decoration 혹은 name mangling  (0) 2012.11.13
[C/C++] switch의 jump table  (0) 2012.11.07

[C++0x] Move Constructor

C++ 2012. 11. 15. 23:38 Posted by byulbit
/*
	Move Constructor( 이동 생성자 )

	c++0x 에는 r-value reference의 도입으로 인해서 Move Constructor가 생겼다.
	이동 생성자의 특징은 복사 대상의 데이터를 지우는 소유권 이전 방식이다.

	기존에 있던 reference는 그래서 이름이 l-value reference 로 불린다.
	l-value 와 r-value 의 차이는 대입식에서 왼쪽에 오느냐, 오른쪽에 오느냐의 차이인데
	
	간단히 말해서 l-value 는 변수라고 보면 되고, r-value는 상수라고 보면 된다.
	기존에 l-value reference는 l-value들만, 즉 변수들만 가리킬 수 있는 반면에
	r-value reference는 r-value 들만, 즉 상수들을 가리킬 수 있다.
	
	int b = 3;
	int& a = b;
	int& c = 3; // error
	
	int&& d = a; // error
	int&& d = 3; // ok.

	r-value reference가 사실 그냥 보기에는 이동 생성자와 전혀 무관해 보이는데, 이것은 기존에
	복사 생성자로 표현하기 어색했던, 소유권 이전방식을 적절히 표현하기에 적합하다.

	무슨 말이냐면,  기존에 auto_ptr 과 같은 소유권 이전의 스마트 포인터들은 값을 이동시키고 싶어하지만,
	적절한 표현이 없어서, const를 제외시킨 아래와 같은 방식으로 구현을 했었다.

	auto_ptr(auto_ptr& a)
	
	문제는 이것이 복사하려는 것인지, 이동하려는 것인지에 대한 명확한 지표가 되기 힘들다는 것.
	이 문제를 해결한 것이 r-value reference(&&)의 등장이다.
r
*/

#include 

class MoveClass
{
public:
	// Default Constructor
	MoveClass() {
		std::cout << "Default Constructor Call" << std::endl;
	}
	
	// Copy Constructor
	MoveClass(const MoveClass& a)	// 반드시 const로 해야 된다. 그렇지 않으면 Move Constructor가 제대로 호출되지 않는다.  
	{
		std::cout << "Copy Constructor Call" << std::endl;				

	}

	// Move Constructor
	MoveClass(MoveClass&& a)
	{
		std::cout << "Move Constructor Call" << std::endl;

	}
};


// 값이 복사되는 곳에서는 일반적으로 복사 생성자가 호출된다.
MoveClass foo(MoveClass& a)
{
	MoveClass local;
	return local;		// 값이 복사 되는데, 우선순위가 존재한다.
						// MoveClass(const MoveClass& a) 형태의 복사 생성자가 존재하고,
						// MoveClass(MoveClass&& a) 형태의 이동 생성자가 존재하면
						// 이동 생성자를 호출한다.
}

int main()
{
	MoveClass a;	// Default Constructor Call

	foo(a);

	MoveClass b(a);					// Copy Consturctor call
	MoveClass b(std::move(a));		// Move Constructor call

}

[C++] C++ name decoration 혹은 name mangling

C++ 2012. 11. 13. 19:48 Posted by byulbit

C++은 함수 오버로딩을 지원하기 때문에 기존의 C스타일의 이름의 함수로는 함수오버로딩을 지원할 수 없다. 

따라서 함수 이름에 대한 데코레이션(decoration) 혹은 맹글링(mangling)이 필요하다.

(데코레이션과 맹글링은 이름을 꾸미는 것, 섞는 것이라는 그런 의미다.)

  

*네임 맹글링 자체가 함수를 구분해주기 위한 용도로써라고 이해하기 되면 약간 거시기 하다.

정확히 이야기하면 C에서도 함수를 부를때 네임 맹글링을 한다. 

"_"를 함수 이름에 붙이는데,  C++기준으로 보면 이건 네임맹글링 수준이 매우 미미하다.


여기서는 함수 오버로딩을 지원하기 위한 측면에서 살펴보겠다.

name mangling, 혹은 name decoration을 하기 위한 것은 컴파일러가 결정하게 되는데,

일반적으로 함수의 파라미터로 함수 지원한다고 생각했었으나 실제 테스트 해보니 그렇지 않았다.

VC++ Compiler에서 반환값도 네임 맹글링에 포함된다.

예를 들어서 아래는 반환값 test2(void) 의 함수 시그니처이다.

 

int __cdecl test2(void)" (?test2@@YAHXZ) -H(int return)

void __cdecl test2(void)" (?test2@@YAXXZ) -X(void return)

float __cdecl test2(void)" (?test2@@YAMXZ) -M(float return)

char __cdecl test2(void)" (?test2@@YADXZ) -D(char return)


보다시피 반환값이 바뀌게 되면 함수의 시그니처 또한 바뀐다.

약간 재밌는 것은 VS에서 C++에서 함수를 호출하는데, 함수의 원형 선언이 없으면 아래와 같은 에러를 
ClCompile 단계에서 낸다. ( C에서는 함수의 선언과 관계가 없이  정의가 존재하면, 함수의 선언과 여부없이 에러를 내지 않는다.)

error C3861: 'test2': identifier not found

위의 에러는 test2 함수를 선언하지 않은 경우로, test2에 대한 identifier 가 없다는 에러인데, 컴파일 단계, 즉 번역 단계에서 에러가 나는 것이라고 추론할 수 있다. 

  또한 C++에서는 이름이 같은 함수끼리의 함수 오버로딩 집합이 존재하며, 오버로딩 집합을 통해서 함수를 부를때 호출하는 측의 시그니처가 동일하지 않아도 오버로딩 해석에 따라서 함수를 부를 수가 있다.

함수에 대한 다른 오버로드한 함수가 존재해서 부르는 함수의 파라미터 개수가 맞는 것이 없다면 아래와 같은 오률르 낸다.
error C2661: 'test2' : no overloaded function takes 3 arguments


함수가 선언이 존재하고, 해당 함수의 정의가 존재하지 않으면 외부 심볼을 찾을 수 없다고 링커 오류를 낸다.
error LNK2019: unresolved external symbol "int __cdecl test3(void) )

#include <stdio.h>


void test2();

int main()
{
//printf("%d", test2());
test2();

}


그렇다면 반환값만 다른 함수의 선언이 있다면 어떻게 될까?
이 경우 아래와 같은 에러를 낸다.
 error LNK2019: unresolved external symbol "void __cdecl test2(void)" (?test2@@YAXXZ) referenced in function _main

이것이 에러가 나는 지점은 함수의 선언이 존재하는데, 정확히 일치하는 해당하는 함수의 정의를 찾지 못했다는 의미다.

자 그러면 A라는 함수의 원형을 선언해놓고, 호출하는 측에서 B라는 함수를 호출, 다른 오브젝트 파일에 B 함수를 정의하면 어떻게 될까?

void test2의 정의를 넣어보았다.

// test2.cpp
int test2()
{
printf("dfdfdf");
return 0;
}

void test2()
{
printf("111111");
}


그러자 이번에는 아래와 같은 에러 메시지가 나온다. 
error C2556: 'void test2(void)' : overloaded function differs only by return type from 'int test2(void)'
error C2371: 'test2' : redefinition; different basic types

즉 오버로딩 함수들은 return type이 다른 것은 인정하지 않는 다는 것을 알 수 있다.
그래서 매개변수가 있는 오버로드 함수를 추가해보았다.

int test2(int a, int b)
{
return a + b;
}

부르는 쪽에서 보았을 때 함수의 선언이 존재하지 않아서 아래와 같은 에러를 낸다.
error C2660: 'test2' : function does not take 2 arguments 

이것은 함수의 오버로딩 집합이 선언에 의해서 판별된다는 것을 의미한다.
C++은 함수의 원형과 관련된 존재가 필수적이라는 것을 알 수 있다.
C의 경우 함수의 심볼이 함수의 이름으로 한정된다.  
즉 여러 오브젝트 파일을 링크 할때, 함수의 이름이 같다면, 해당하는 함수를 추가하기가 힘들다.
아래와 같이 심볼이 중복 정의 되어 있다고 에러를 내기 때문이다.
 fatal error LNK1169: one or more multiply defined symbols found

그렇다면 C++은 어떠할까?
C++의 경우 함수원형의 선언이 필수적이지만, 시그니처가 일치한다면 해당하는 함수를 부를 수 있으며,
오버로딩을 이용하여 함수의 이름이 같지만, 매개변수가 다른 것들을 추가할 수 있다.
물론 이 경우도 함수를 사용할시에 사용할 함수의 선언이 필수적이다.
 

C에서는 부르는 쪽에서 해당 함수가 존재하지 않으면 당당히 링커에서 오류를 낸다. 
 error LNK2019: unresolved external symbol _test1
위의 에러를 보면 알겠지만, "_함수이름" 으로 C에서는 네임 맹글링을 하며, 
즉 C는 오버로딩이 지원이 안된다.
하지만 해당하는 심볼이 존재한다면 호출이 가능한데 예를 들어서 함수의 정의 자체가

int test(int a)
{
printf("test(int a)");
return 0;
}

이렇게 정의가 되어 있더라도, 해당하는 함수를 다른 매개변수로써 호출할 수 있다.
int main()
{
test("Dfdfdfdf",2);

}

위의 코드는 test(int a)를 호출한다. 이것이 가능한 이유는 함수를 호출 할때는 함수의 심볼을 보고 찾기 때문이다.  즉 C에서는 test(int a)나 test(const char*, int) 로 추정되는 것들이나 함수의 심볼 자체는 _test로 같은 것으로 여겨지게 된다.

보통 C++에서 함수의 매개변수와 반환값 등으로 네임 맹글링을 한다고 생각할 때, 그럼 C++에서는 어떻게 가변인자 함수를 만들고 부를까? 라는 의구심이 생길 수 있다.  왜냐면 함수의 심볼이 같아야 함수의 정의를 찾을 수 있고, 따라서 함수 호출이 가능하기 때문이다.
테스트 해본 결과 재밌는 사실은 매개변수의 갯수 뿐만 아니라, 가변인자 함수인 경우의 네임맹글링도 존재하는 것이다.

void sprintfs(char *Result, const char *Format,  ...  ); 

이런 함수가 존재할 경우에

sprintfs("%d %d %d %d %d", &a, &b, &c, &d, &e); 이렇게 함수를 호출하는 경우와
sprintfs("%d %d %d %d", &a, &b, &c, &d);  이런 경우에 대해서 가변 인자 함수가 아니라면 심볼이 일치 하지 않아서 함수를 찾을 수 없어야 한다.

확인해본 결과 가변인자 함수의 경우 시그니처는 sprintfs@@YAXPADPBDZZ 로 동일하며 부르는 쪽의 매개변수가 달라져도,  심볼은 같으므로, 호출이 가능하다.