Object-Oriented Programming 2 with Game Programming

SYiee·2023년 1월 1일
0

게임엔진

목록 보기
4/5

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

  1. Trigraph replacement : trigraph sequences를 문자로 바꾼다
    (e.g. ??( = [, ??! = | )
  2. Line splicing
  • new line sequence 같은 것을 컴파일러가 잘 이해할 수 있도록 수정
  • escaped newline sequences를 컴퓨터가 이해할 수 있도록 logical한 라인으로 바꾸어줌
  1. Tokenization
  • 자기들이 이제 뭔가 봄 이렇게 나누어서 바꾸어주어야 하는 것들
  • 텀파일러한테 이 코드를 잘 바꿔
  1. 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

  • foreach 스타일의 declaration

  • i<5 : i가 늘어났을 때 i를 바꾸어주어야 함

    그래서 보통 벡터의 사이즈를 쓰는데 사이즈랑 인덱스랑 일치하지 않을 수도 있다. 그래서 2번째 사진과 같이 구현

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

✅ 계산 방법

  1. -118.625

  2. "Sign 부호부"
    sign bit는 1(음수)표시하고 양수로 바꾼다. 118.625

  3. (two’s complement)로 변경 118.625 = 1110110.101

  4. "Fraction 가수부"

  • 소수점을 앞쪽으로 쭉 옮긴다. 그리고 정규화를 해준다. 아래 예시에서는 2^6을 곱함.
  • 정규화된 식에서 소수점 오른쪽 부분을 가수부에 채워준다.
  • Mantissa = 11011010100000000000000

  1. "Exponent 지수부"
  • bias : 127, 지수 : 6
  • e = 127 + 6 = 133
  • 133을 2진수로 변환하고 이를 지수 부분에 채운다.
  • 소수점 표현은 approximation이 강하다
  • 딱 떨어지는 소수점이 많지 않다.

🖇 Reference

해당 포스트는 강형엽 교수님게임엔진기초 [GameEngine-22-2] 수업을 수강하고 정리한 내용입니다. 잘못된 내용이 있다면 댓글로 알려주시면 감사하겠습니다😊

profile
게임 개발자

0개의 댓글