1. 생성자가 protected/private인 경우


생성자가 protected/private인 경우 일반적으로는 개체를 생성할 수가 없다.



이런 경우는 별도의 public static 함수를 제공해서 개체를 리턴시키는 경우이다.



참고링크: https://isocpp.org/wiki/faq/ctors#named-ctor-idiom



2. 소멸자가 protected/private인 경우


소멸자가 protected/private인 경우엔 stack에 해당 클래스의 변수를 생성할 수 없다.

이는 컴파일 시 stack 변수가 있는 bracket이 끝나면 소멸자를 호출해야 하는데, 소멸자가 protected/private 이어서 호출할 수 없기 때문이다.



이 경우는 해당 변수를 heap에 생성하면 된다. Heap 변수는 bracket이 끝나도 소멸자가 불리지 않기 때문이다.


다만 이 경우도 delete를 통해 heap 변수를 제거하려 하면 에러가 나기 때문에, 별도의 자기 자신을 파괴하는 destroy 함수를 제공해야 한다.




3. Protected 생성자/소멸자인 경우


Protected 생성자/소멸자인 경우에는 stack에 해당 클래스의 개체를 생성할 수는 없지만, 상속한 경우에는 가능하다.




혼자 프로그래밍을 하는 경우는 한 파일 내에서 선언과 구현을 해버리는 경우가 많으나, 


일반적인 경우는 선언부와 구현부를 나눠서 각각 헤더 파일과 소스 파일에 작성하게 된다.


이는 라이브러리 형태로 배포하게 될 경우 소스는 배포하지 않아 구현을 숨겼더라도 헤더파일만 보고 해당 코드의 기능을 쓸 수 있기 위함이기도 하고, 소스를 배포하더라도 가독성을 위해서 필요한 것이다.


이때 주의해야 할 내용을 정리해 본다.



1. 인라인, 템플릿 등 컴파일 시간에 어셈블리 코드가 치환되는 경우, 헤더 파일에 구현과 선언이 같이 있어야 한다.


아래 구현을 보자.

<헤더파일>




<소스파일>



이 헤더파일을 include하고 Stack 클래스를 가져다 쓰면 잘 컴파일 될 것 같지만 에러가 발생한다.

(만일 이 코드가 template이 아니었다면 정상 동작한다)


이는 가져다 쓰는 쪽을 컴파일 할 때 컴파일러가 헤더 파일을 보고 template을 치환해야 하는데, 구현부의 소스파일은 이미 컴파일 된 상태이기 때문에 컴파일러가 치환할 수 없기 때문이다.


따라서 이 코드가 잘 동작하려면 소스파일 내용을 헤더파일 내부로 옮겨야 한다.


template의 경우 선언/구현 둘 다 키워드가 필요한데,

inline의 경우 inline 키워드는 선언부, 구현부 한 군데에만 있거나 두 군데에 있어도, 만일 둘 다 inline이 없어도 선언과 구현이 모두 헤더파일에 있으면 암시적으로 inline이 되는 것 같다. (링크)



2. 디폴트 파라미터를 사용하는 경우 선언부에만 적어주어야 한다.


디폴트 파라미터를 선언부와 구현부 모두에 쓰면 컴파일 에러가 발생한다.


디폴트 파라미터를 선언부에는 쓰지 않고 구현부에만 쓰는 경우,

컴파일러가 함수 호출시 선언부를 보고 함수 호출이 올바른지 체크하게 되는데, 

선언부에는 디폴트 파라미터를 쓰지 않았으므로 호출에 대해 인자가 모자라다는 에러를 내게 된다.

즉, 디폴트 파라미터를 쓸 수 없게 된다.




3. 정적 멤버 변수와 정적 멤버 함수


정적 멤버 변수의 경우 외부 선언은 구현 파일에 작성한다.


정적 멤버 함수의 경우 선언부에만 static 키워드를 넣고 구현부에는 넣지 않는다.

구현부에 static 키워드를 넣으면 C의 static 키워드가 된다.



4. 상수 멤버 함수


상수 멤버 함수의 const 키워드는 선언부와 구현부 모두 넣는다.


5. 가상 함수


가상 함수를 나타내는 virtual은 선언부에만 적는다. (구현부에는 X)

1. 암시적 형변환


C에서는 void 포인터를 다른 포인터로 넣거나, 다른 포인터에 void 포인터를 넣는 것이 컴파일 에러 없이 암시적 형변환 된다.

아래 코드는 C에서는 문제없이 컴파일 된다.



단 C++에서는 위의 코드가 컴파일 되지 않는다. 


2. 명시적 형변환


C에서는 (자료형) 으로 대부분의 형변환이 성공한다. 아래처럼 메모리 크기가 다른 자료형에 대해서도 형변환이 성공하고, 이는 버그를 유발할 수 있다.



따라서 C++은 용도에 따라 cast 명령을 나눴다.


static_cast

: 컴파일 시점에 연관성이 있는 형끼리만 변환시킨다.


dynamic_cast

: 실행시간에 타입을 확인하여 캐스팅을 수행한다. RTTI 기술을 사용하는데, 클래스 상속이 이루어지는 경우 컴파일 시간에 타입을 알 수 없기에 사용한다.


const_cast

: 상수성을 제거할 때 사용한다.


reinterpret_cast

: C의 형변환과 동일한 것으로 되도록 사용하지 않는 것이 좋다.

일반적인 함수 선언은 다음과 같다.



이와 달리 return type을 auto로 적고 인자 뒤에 -> 를 적고 적을 수 있다.



오픈 소스를 보다 보면 이런 코드들이 나온다.

'프로그래밍 > C++' 카테고리의 다른 글

선언부와 구현부를 분리할 때 주의해야 할 것 정리  (0) 2017.11.08
C와 C++의 cast  (0) 2017.10.08
함수를 못 쓰게 하기  (0) 2017.10.08
인라인 함수  (0) 2017.10.08
함수 오버로딩  (0) 2017.10.08

때때로 암시적 형변환을 프로그래머가 금지해야 하는 경우가 있다.


이를 테면 int형 인자를 받는 함수에 double형 인자를 넣게 되면 암시적 형변환이 되는데, 이를 막기위해선 두 가지 방법이 있다.


1. double 형 함수를 선언만 하고 구현하지 않는다.



이 경우 컴파일 시 foo(double)로의 참조가 정의되어 있지 않다고 링크 에러가 발생하게 된다.


2. C++ 11의 delete function 기능을 쓴다.



이 경우 위와 달리 컴파일 시점에 에러가 발생한다.


라이브러리를 제작해서 배포하려 하면 2번 방법시에만 에러가 나니 약간 다르다.

'프로그래밍 > C++' 카테고리의 다른 글

C와 C++의 cast  (0) 2017.10.08
후위 반환 형식 trailing return type/ suffix return type  (0) 2017.10.08
인라인 함수  (0) 2017.10.08
함수 오버로딩  (0) 2017.10.08
디폴트 파라미터  (0) 2017.10.08

함수는 인자 값을 복사하고, 함수로 이동한 다음, 실행을 마치고 다시 돌아오는 반면,

인라인 함수는 컴파일 시점에 기계어 코드를 치환하기 때문에 성능 하락이 없다.


다만 인라인 함수를 사용하면 코드가 길어질 수 있기 때문에, 보통 짧은 함수들에 사용한다.


다른 파일에 있는 인라인 함수를 사용할 때는 헤더 파일 내에 구현부가 꼭 있어야 한다.

(같은 파일 내에는 상관 없다.)


위에서 인라인 함수는 "컴파일 시점"에 기계어 코드를 치환한다고 했는데, 헤더에 선언하고 소스에 구현한다면 이미 컴파일된 소스에서 어디가 인라인 함수인지 알 수가 없기 때문이다.

'프로그래밍 > C++' 카테고리의 다른 글

후위 반환 형식 trailing return type/ suffix return type  (0) 2017.10.08
함수를 못 쓰게 하기  (0) 2017.10.08
함수 오버로딩  (0) 2017.10.08
디폴트 파라미터  (0) 2017.10.08
printf 형식지정자  (0) 2017.10.03

C와 달리 C++은 함수 오버로딩을 지원한다. 


함수 오버로딩은 함수 인자를 통해 동일한 이름의 함수를 구분하게 해준다.

함수 리턴 타입으로는 오버로딩이 되지 않는데, 함수를 부를 때 리턴값으로는 구분이 되지 않기 때문이다.


오버로딩 코드의 어셈블러 코드를 보면, 오버로딩은 결국 인자에 따라 컴파일러가 다른 이름의 함수를 여럿 생성하는 것이다.


예시 코드



아래는 어셈블리 코드이다.

보면 foo(int)는 fooi로, foo(double)은 food로 생성한 것이다. 

그리고 각각 아래에서 호출하고 있는 것이 보인다.


.file "practice.cpp"

.text

.globl _Z3fooi

.def _Z3fooi; .scl 2; .type 32; .endef

.seh_proc _Z3fooi

_Z3fooi:

.LFB0:

pushq %rbp

.seh_pushreg %rbp

movq %rsp, %rbp

.seh_setframe %rbp, 0

.seh_endprologue

movl %ecx, 16(%rbp)

movl $0, %eax

popq %rbp

ret

.seh_endproc

.globl _Z3food

.def _Z3food; .scl 2; .type 32; .endef

.seh_proc _Z3food

_Z3food:

.LFB1:

pushq %rbp

.seh_pushreg %rbp

movq %rsp, %rbp

.seh_setframe %rbp, 0

.seh_endprologue

movsd %xmm0, 16(%rbp)

movl $0, %eax

popq %rbp

ret

.seh_endproc

.def __main; .scl 2; .type 32; .endef

.globl main

.def main; .scl 2; .type 32; .endef

.seh_proc main

main:

.LFB2:

pushq %rbp

.seh_pushreg %rbp

movq %rsp, %rbp

.seh_setframe %rbp, 0

subq $32, %rsp

.seh_stackalloc 32

.seh_endprologue

call __main

movl $1, %ecx

call _Z3fooi

movq .LC0(%rip), %rax

movq %rax, %xmm0

call _Z3food

movl $0, %eax

addq $32, %rsp

popq %rbp

ret

.seh_endproc

.section .rdata,"dr"

.align 8

.LC0:

.long -1717986918

.long 1072798105

.ident "GCC: (GNU) 7.1.0"



'프로그래밍 > C++' 카테고리의 다른 글

함수를 못 쓰게 하기  (0) 2017.10.08
인라인 함수  (0) 2017.10.08
디폴트 파라미터  (0) 2017.10.08
printf 형식지정자  (0) 2017.10.03
표준 입력 cin  (0) 2017.08.06

C++에서는 아래와 같이 디폴트 파라미터를 줄 수 있다.



이때 몇 가지 주의사항이 있다.



1. 디폴트 파라미터는 뒤에서부터 줘야 한다.


즉, int foo(x, y = 0, z = 0) 은 되지만 int foo(x = 0, y, z = 0) 은 에러다.


뒤의 방식으로 썼을 경우 foo(1, 1)이라고 썼을 때 x가 생략된 것인지 z가 생략된 것인지 알 수 없다.



2. 선언부와 구현부가 나뉘어져 있을 경우 "선언부"에만 넣어야 한다.


만일 둘 다 디폴트 파라미터를 입력하면 컴파일 에러가 발생한다.


구현부에만 표기하면 디폴트 파라미터를 사용할 수 없게 된다.


외우기보단 이유를 생각해보면 되는데, 구현부는 소스코드에 있기에 미리 컴파일 되어 있으면 컴파일러가 확인할 수 없기에 헤더 파일의 선언부만 컴파일러가 확인할 수 있다. 따라서 구현부에만 넣어 놓은 것은 컴파일 시점에 영향을 미칠 수 없다.

'프로그래밍 > C++' 카테고리의 다른 글

인라인 함수  (0) 2017.10.08
함수 오버로딩  (0) 2017.10.08
printf 형식지정자  (0) 2017.10.03
표준 입력 cin  (0) 2017.08.06
표준 출력 cout  (0) 2017.08.06

+ Recent posts