Introduction
지난 시간에 이어서 c++에 관련된 OOP적 요소와 여러가지 기능들을 살펴보는 챕터입니다. Compiling과정, Smart Pointer, Floating Point의 사용 이유와 계산 방법, Virtual Function과 Override 등에 대해 정리했습니다.
C++
Why c++ is good?
- C++ is known to be a very powerful language.
- One of C++'s advantages is how scalable it could be.
- You can directly manage the memory, thread, storages, etc.
- C++ has a huge community and very big job market.
- 그래픽스 코드할 때도 필요하다.
- 그래픽 프로그래밍 난이도가 올라가는데 그 이유는 메모리 관리를 직접하기 때문인데. 시스템 콜에 익숙하지 않으면 할 수가 없다
⭐ Compile 과정
C++ preprocessor
Trigraph replacement
: trigraph sequences를 문자로 바꾼다
(e.g. ??( = [, ??! = | )
Line splicing
- new line sequence 같은 것을 컴파일러가 잘 이해할 수 있도록 수정
- escaped newline sequences를 컴퓨터가 이해할 수 있도록 logical한 라인으로 바꾸어줌
Tokenization
- 자기들이 이제 뭔가 봄 이렇게 나누어서 바꾸어주어야 하는 것들
- 텀파일러한테 이 코드를 잘 바꿔
Macro expansion and directive handling
- 매크로 썼을 때 디렉티브 핸들링
- #pragma once : 이런것들이 전처리 단계에서 컴파일러에게 알려줌
혹시나 다른 곳에서 include가 되었으면 다른 곳에서는 하지마!
- #pragma comment
C++ compiler
: 기계어로 바꾸어준다. 최종적으로 어셈블리 코드가 나온다
- 컴파일 타임에 semantic analysis and code generation을 한다
- 컴파일 타임에 에러가
- Syntax errors : 프로그래밍에서 쓰자고 약속한것을 빼먹었을 경우
→ ;
- Semantic errors : 존재하지 않는 문법을 사용
→ a+b =c⇒ 컴파일 에러가 나오면 중지를 시킨다
C++ assembler
: 어셈블리어를 머신코드( .o
, .obj
)로 변경
C++ linker
- 여러개의 오브젝트 파일을 연결해 실행파일을 제작
- 링커는 실행파일, 라이브러리 파일, 다른 오브젝트 파일을 링크해준다.
C++ extension
Auto
-
deduce its type
-
어떤 타입인지 추측하도록 만듬
-
남발하면 함께 하는 팀원들이 힘들 수 있음
-
너무나 자명한 친구들이 들어왔을 때 많이 사용
-
장점 ) Robustness, Performance, Usability,Efficiency
-
남용 금지
(abuse of auto can increase the potential for code to behave in an unintended ways.)
Nullptr
: 비어 있는 것을 가르키는 포인터
- pointer : 메모리 영역에 직접적으로 접근이 가능하다
- 왜 필요할까?
- ex) 캐릭터의 몇번째 본을 움직여~ 이미 캐릭터가 존재한다고 가정을 하고 있는데 만약 캐릭터가 존재하지 않으면 알 수 없는 메모리 영역을 접근하는 것이기 때문에 에러가 나니… 미리 캐릭터 메모리 존재해? 라고 물어본당
- 2번 사진 예시
함수 이름은 다 같지만 무엇을 호출할지는 파라미터가 무엇이 오느냐에 따라 컴파일러가 알아서 불러준다 → 다형성
- 과거에는 NullPtr을 그냥 0으로 사용, 가끔 (void) (char)로 형변환
- 그러나 그냥 비어있는 nullptr로 썼는데 정수 0으로 받아들여서 실행이 될 때가 있다.
- 그래서 C++ 11부터는 명시적으로(explicit) nullptr을 null pointer을 나타내도록 함
Range-based for Loops
Iterator
- Iterators are used to point at the memory addresses of STL containers.
- STL : 잘 구현이 되어 있어서 우리가 가져다 쓰면 되지만 메모리 관리를 우리에 맞게 뜯어 고쳐야 하기 때문에 어떻게 돌아가느지 알아야 한다.
- iterator : 메모리 주소를 이용해 루프를 돈다
Virtual, final and override
override and final
override
- virtual method를 overide할 떼
- base function에서 구현을 해두었는데 다음 함수에서 이것을 상속을 받으면서 새로 구현하는 것. 같은 이름인데 다르게 작동하도록 만듬
- 1번 그림
- 실제 실행할 때 Derived 인스턴스에서 f() 함수를 실행시키면 최근에 쓴 함수인 밑에 새로 만든 것을 실행됨.
- 코드를 작성하는 사람이 볼 때 이게 오버라이드 된건지 아닌지 잘 모르기 때문에 override를 붙여서 확실히 해주는 것
- 2번 그림 에러 항목
-
A에서 foo() final을 했는데 B클래스에서 foo를 override해서 에러가 뜬다
-
A 클래스에서 bar()함수를 final이라고 했는데 베이스 클래스에 없는 함수 임에도 final이라 해서 오류
-
B 클래스를 final이라 했는데 c에서 또 상속을 받으려 해서 오류
⇒ final - 더 이상 상속 금지, 더 이상 오버라이드 금지
❓ 왜 쓰나요?
→ 함수를 계속 증식시키는 것을 방지 + 유지 보수를 위해 사용
→ 남들이 함부로 가져다 쓰는 것을 막고 가독성을 위해
Enum
- switch 에 보통 배열의 인덱스를 넣어서 했는데 Enum을 사용하면 가독성이 높아진다
Standardized Smart Pointers
일반적인 포인터를 사용하느것보다 몇가지 이득이 있다
Shared Pointer
- 여러 포인터가 가르킬 수 있다.
❓ 언제 해제가 될까?
: "Reference Counting" 방식을 사용해서 참조가 되면 count를 올리고 해제가 되면 참조값을 내리면서 count 값이 0이 되면 object를 소멸시킨다.
💣 문제
= Circular reference
참조하는 대상이 서로 물려 있어 할당 해제가 되지 못하는 상태
- 포인터가 누군가를 가르키고 있으면 쓰고 있는 건데 0이 되어 버렸다
- 그러면 안 쓰는건데 해제하는 것을 잊으면 영원히 그상태로 남아 있게 됨.
- 아무도 찾을 수 없는 공간에 있음....!
memory leak : 메모리가 해제가 되지 않고 occupied 된 상태로 남아있는 것
→ 너무 심해지면 블루스크린 발생!
✔ 해결 : 나를 가르키고 있는 애가 0이 되고 그 시간이 어느정도 흘렀을 때 나를 자동으로 해제해라
Weak Pointer
- Circular reference problem을 해결
- 나를 가르키는 것이 있을 때 해제를 안 하는데 여러 것들이 나를 가르킴. 그래서 영원히 살아남음.
- 그런데 잃어버려서 외부에서 접근이 불가능하다.
- 누군가가 나를 가르키고 있더라도 Circular이면 해제할 수 있도록 함.
- weak_ptr을 사용해서 너가 혹시 shared memory이면 너가 나를 가르키더라도 나는 해제가 가능
Unique Pointer
- 나를 가르킬 수 있는 것은 오직 너뿐이야!
- ex) 스피커가 갑자기 와이파이 인터넷에서 들어오는 것을 다 송출해버림
Lambdas
간단한 형태의 함숭여서 대충 쓸건데 선언하고 그러기 귀찮을 때 사용
반복적으로 a+b해서 쓸건데 함수로 만들기 귀찮을 때 사용
Move Semantics and Rvalue References
- 벡터 요소를 곱하는게 있을 때
- 1 2 3 4 5에 전체에 5를 곱해야할 때 생성된 다음에 나오는게 새로운 벡터인데 이게 비효율
- output 벡터는 temporary 벡터인데 v에 삽입이 되는데 v에 쓰고 말건데 temporary 벡터를 만드는 것이 비효율이다.
✔ 해결책
- output 자체도 미리 메모리 공간에 만들고, 받아온 것을 초기화하고 거기에 넣어줌
- 이렇게 하려니까 구조 자체의 변화가 필요하다.
- 어디선가 선언한 output 벡터를 파라미터로 받아오는 형식으로 바꾸어야 해서 코드가 복잡해지니 유지 보수가 어려울 수 있다.
move semantics
→ 그래서 최근 이를 해결하기 위해 리턴된 것을 pointer나 refenrence
☑️ lvalue : 어디에 있는 지 알고 있는 것.
☑️ rvalue : 어디에 있는지 알 수 있는 방법이 없는 것
- int var 10; → 인지 가능 lvalue
- 위에 있는 그냥 10 → rvalue, 분명 어딘가에 있지만 접근 불가 var을 통해서만 접근 가능
- We can’t do ‘int var; 10 = var;’ because rvalue ‘10’
- rvalue 자체는 카피가 불가능하다. 카피하려면 var에다가 10을 탑재한 상태에서 int var2를 만들고 복사를 해야한다.
- 모든 것을 lvalue처럼 가정하고 한다?
- deep copy - 컨테이너뿐만 아니라 달고 있는 데이터까지 복사를 해주어야 한다.
Rvalue References
- logocally unnecessary copying은 피해야 한다.
- rvalue는 s_final이 아니라 s_1 + s_2 string에 직접 접근을 하고 싶은 것
int main()
{
std::string s_1 = "I am";
std::string s_2 = "SYiee";
std::string s_final = s_1 + s_2;
}
- 기존에는 int& asdf = 100이게 불가능 했는데 이제 가능해졌다
- &&을 두개를 써서 한다! rvalue에 직접 접근하기 위해서 필요한 레퍼런스
s_final-rref → s_1 +s_2에 접근할 수 있는 주소가 된다.
int main()
{
std::string s_1 = "I am";
std::string s_2 = "SYiee";
std::string&& s_final = s_1 + s_2;
}
Move semantics
- rvalue refernce를 이용해서 복사
- 새로운 rvalue를 temporary value로 만들지 않고 바로 접근할 수 있는 상황이 되었기 때문에 복사가 안 일어나서 copy에서 이득을 가져올 수 있다.
If-Init statement
- 무언가를 할건데 초기화가 되어 있는지 미리 확인
초기화가 안되어 있으면 오류가 날 수 있기 때문에 if문 안에서 바로 초기화를 검사
Tuple
- 데이터형이 서로 다른 형을 갖음
- 매번 클래스 만들기 귀찮을 때
- 함수에 데이터를 넣을 건데 그것때매 클래스 만드기 귀찮을 떄
Numeric Representations
Numeric bases
- 386 → 10진수
- 0b1011 → 이진수
- 0xA013 → 16진수
Signed and unsigned integers
- 32-bit unsigned integer is 0x00000000 to 0xFFFFFFF
- 부호를 표현하고 싶으면 맨 앞 bit를 sign bit로 만들어 사용
→ 0이면
💣 단점
- 별도의 회로 필요
-가 없을 때의 사칙연산하고 +가 없을 때의 사칙 연산하고 다른 회로를 사용해야함
- 표현할 수 있는 bit가 한자리 줄어듬
- 0이 2개가 생긴다.
⇒ 해결 ) 2의 보수체계 사용 two’s complement notation.
two’s complement notation
- 0이 두개가 되는 현상이 사라짐
- 별도의 회로 필요 없이 똑같은 회로 사용
Fixed-point notation
- 분수나 소수를 표현할 때 앞에는 정수도
- . 앞쪽과 뒤쪽에 표현할 수 있는 bit를 각각 몇개씩 할당
- 표현할 수 있는 값의 범위가 한정이 됨
- 정밀도가 떨어지게 된다.
- 수학적인 연산으로 지지고 볶으면 더 정밀도가 떨어짐
- 정밀도를 높일라면 더 많은 비트를 사용해야하는데 낭비
- 0.004이런거 표현하려면 앞에 정수 쪽 너무 낭비
⭐ Floating-point notation
Mantissa
Exponent
: decimal point의 위치를 정함
Sign bit
✅ 계산 방법
-
-118.625
-
"Sign 부호부"
sign bit는 1(음수)표시하고 양수로 바꾼다. 118.625
-
(two’s complement)로 변경 118.625 = 1110110.101
-
"Fraction 가수부"
- 소수점을 앞쪽으로 쭉 옮긴다. 그리고 정규화를 해준다. 아래 예시에서는 2^6을 곱함.
- 정규화된 식에서 소수점 오른쪽 부분을 가수부에 채워준다.
- Mantissa = 11011010100000000000000
- "Exponent 지수부"
- bias : 127, 지수 : 6
- e = 127 + 6 = 133
- 133을 2진수로 변환하고 이를 지수 부분에 채운다.
- 소수점 표현은 approximation이 강하다
- 딱 떨어지는 소수점이 많지 않다.
🖇 Reference
해당 포스트는 강형엽 교수님의 게임엔진기초 [GameEngine-22-2] 수업을 수강하고 정리한 내용입니다. 잘못된 내용이 있다면 댓글로 알려주시면 감사하겠습니다😊