Back-end/C++

22. 03. 22 - 파일 분할, inline 함수

giggs 2022. 4. 2. 12:42

 

파일 분할

 

  • 지금까지 실습하면서 새로운 오브젝트를 만들고 클래스를 만들 때마다
  •  main.cpp 파일을 매번 만들어서 그 안에서 클래스를 만들고 함수를 선언하고 구현해서 실습했었다.
  • 요즘 언어들은 선언부와 구현부를 같이 만든다. 

 

 

 

BUT! 

 

 

 

C와 C++ 은 태생적으로 선언부와 구현부를 구별해서 - 파일 분할 형식으로 만든다! 

  • 선언부란? - 이런 게 있어; 하고 알려주는 문서 – 컴파일러에게 알려줌
  • 구현부란? - 선언부의 그 문서를 실제 구현한 문서 - 실제 코드로 만든 문서 

 

 

이처럼 파일 분할 형식으로 선언부와 구현부를 구별해서 만들기 때문에

  • 선언부를 저장하는 헤더(. h ) 파일
  • 구현부를 저장하는 클래스(. cpp ) 파일 
  • 2가지로 나눈다.
  • 이처럼 나눠서 컴파일하는 것이 => 분할 컴파일

 

 

 


 

 

 

실습 순서 큰 틀 잡기 - 

 

1. main.cpp 파일 만들어서 코드 싹 다 입력 - class 부분 / main 부분 다 입력

2. 헤더 파일 만들기 - main.cpp에 있는 class 부분만 긁어와서 넣어준다.

3. main.cpp 에있는 main부분에서 오류 난다. - 사용한 클래스나 함수들이 어디에 있는 누구의 것인지 몰라서

4. #include "DynamicArray.h"로 위치 알려준다. - < >는 기본적으로 제공되는 파일, " " 는 사용자가 만든 파일

5. DynamicArray.cpp 파일을 만들어서 DynamicArray.h 에 있는 함수의 구현 부를 가져온다.

6. h 파일에는 선언부가 //. cpp파일에는 구현부가 위치하도록 해준다.

 

 

 

 


 

 

 

 

클래스 부분과 메인 부분이 같이 있는 main.cpp 파일이 있다. - 파일 분할해보자

 

#pragma once
#include <iostream>

using namespace std;


// 클래스 부분
class DynamicArray {
private:
    int _size;
    int* _arr;

public:
	
    DynamicArray(int size) {   
        _size = size;

        _arr = new int[_size];
    }
   
    DynamicArray(DynamicArray& ref) {
        cout << "복사생성자" << endl;
        _size = ref._size;

        _arr = new int[_size];

        for (int i = 0; i < _size; i++) {
            _arr[i] = ref._arr[i];
        }
    }

    ~DynamicArray() {   
        delete[] _arr;
    }

    int GetSize() {
        return _size;
    }

    int GetIndex(int index) {
        return _arr[index];
    }

    void SetIndex(int index, int value) {

        _arr[index] = value;
    }
    
    void info() const {

        for (int i = 0; i < _size; i++) {
            cout << "array[" << i << "] = " << _arr[i] << endl;
        }
    }
};


//메인 부분
int main() {
    
    DynamicArray array(10);

    for (int i = 0; i < array.GetSize(); i++) {
        array.SetIndex(i, i);
    }

	array.info();

    return 0;
}

 

 

 

 


 

 

 

 

1. 헤더 파일을 만들고 - main.cpp에 있는 클래스 부분을 옮겨준다.

 

 

 

 

 

 

DynamicArray.h ---

 

#pragma once
#include <iostream>

using namespace std;

//클래스 부분
class DynamicArray {
private:
    int _size;
    int* _arr;

public:
    DynamicArray(int size) {  
        _size = size;

        _arr = new int[_size];
    }


    DynamicArray(DynamicArray& ref) {
        cout << "복사생성자" << endl;
        _size = ref._size;

        _arr = new int[_size];

        for (int i = 0; i < _size; i++) {
            _arr[i] = ref._arr[i];
        }
    }

    ~DynamicArray() {   
        delete[] _arr;
    }

    int GetSize() {
        return _size;
    }

    int GetIndex(int index) {
        return _arr[index];
    }

    void SetIndex(int index, int value) {

        _arr[index] = value;
    }
    
    void info() const {

        for (int i = 0; i < _size; i++) {
            cout << "array[" << i << "] = " << _arr[i] << endl;
        }
    }
};

 

 

 


 

 

 

2. 클래스 부분을 없애버려서 main.cpp에 오류 발생 - #include "DynamicArray.h"로 위치 알려주기

 

 

main.cpp  ---

 

#include "DynamicArray.h"

//메인 부분
int main() {
    
    DynamicArray array(10);

    for (int i = 0; i < array.GetSize(); i++) {
        array.SetIndex(i, i);
    }

 	array.info();

    return 0;
}

 

 

 


 

 

 

3. main.cpp 출력 확인

 

 

main.cpp파일에 있던 클래스 부분을 헤더파일로 옮긴 뒤 -> #inlcude "~.h"해주고 출력 Test

 

 

정상출력 확인 

good :)

 

 

 

 


 

 

 

 

선언부와 구현부 분리해주기

 

 

 

DynamicArray.h 파일에 있는 함수 빼오기

 

 

1. DynamicArray.h 파일에 있는 함수 info( )를 - 새롭게 만든 DynamicArray.cpp파일에 옮겨주었다.

 

 

헤더파일에있던 info ( ) 함수를 -> cpp파일로 옮겨주었다.

 

 

 


 

 

 

 

2. 어디에 있는 누구의 info( ) 인지 표시를 안 해주어서 오류남

 

  • 클래스 밖으로 함수가 나온 경우 - 어디의 info ( ) 함수인지 알 수 없다.
  • 어디에 속해있는 함수인지 지정 필요 - DynamicArray :: 

 

 

옮긴 info ( ) 함수에 대해여 누구의 info ( ) 함수인지 체크

 

 

 


 

 

 

3. DynamicArray.h 파일 - info( ) 함수 구현부{ }를 없애고 선언부만 놔두면 된다. 

 

 

구현부는 cpp파일로 보내고 선언부만 남은 - 헤더파일에서의 info ( ) 함수 모습

 

 

 


 

 

 

추가로 한 개 더 빼오면서 실습

 

 

DynamicArray.h에서 DynamicArray생성자 -> DynamicArray.cpp로 빼오기 

  • DynamicArray :: 로 표시 필요

 

 

헤더파일에서 -> cpp파일로 함수 빼오고, 누구의 함수인지 표시해주기

 

 

 


 

 

 

DynamicArray.h에는 구현부 { } 없애주고 선언부만 표시해주기

 

 

구현부는 cpp파일로 보내고 선언부만 남은 - 헤더파일에서의 함수 모습

 

 

 

 

이처럼 

  • DynamicArray . h --- (  헤더 파일  )에는 선언부가 있고
  •  DynamicArray . cpp --- (   cpp파일   )에는 구현부가 있도록 구별해야 한다!

 

 

 

 


 

 

 

 

Question. : #pragma once?

 

 

 

 

 

 

 


 

 

 

Answer. 중복 포함하지 마라는 의미!!

 

 

A.h 파일 안에 #include "DynamicArray.h" 되어있는 상태인데

 

 

 

A헤더파일에 DynamicArray헤더파일은 include해준 모습

 

 

 

main.cpp에 와서 이렇게 #include "DynamicArray.h"와 #include "A.h" 해버리면

 

 

main.cpp 에서 include 해준 모습

 

 

main.cpp는 DynamicArray.h 을 2번 포함하게 되는 것이다. - 있는 게 또 있다고 오류남

이런 상황을 막아주기 위해 #pragma once 사용하는 것 - 중복 포함하지 마라 

 

 

 


 

 

inline함수 

 

 

구현부와 선언부를 분리하는 특징은 C++ 특징 - java는 구현부 선언부 구분하지 않는다.

 

 

이처럼 함수를 - 구현부와 선언부로 분리해서 사용할 경우 - 2가지로 구분된다.

  • 클래스 내부에 함수 선언해서 사용하는 경우
  • 파일 분할 후 함수 꺼내와서 사용하는 경우

 

 

두 가지 경우 - 컴파일러는 각각 다른 요청으로 받아들인다.

클래스 내부에 선언하게 되면 inline 함수를 만들어 달라는 요청으로 받아들인다.

 

 

 

 


 

 

 

인라인 함수란?

 

  • 프로그램의 실행 시간을 줄이기 위한 C++ 향상 기능.
  • 함수는 호출되는 모든 위치에서 컴파일러가 해당 함수 정의를 대체할 수 있도록 - 함수를 인라인으로 만들도록 컴파일러에 지시할 수 있다.
  • 컴파일러는 런타임에 함수 정의를 참조하는 대신, 컴파일 시간에 인라인 함수의 정의를 대체.
  • 이것은 함수를 인라인으로 만들기 위해 컴파일러에 제안하는 것이다.
  • 함수가 크면(실행 가능한 명령어 등의 측면에서) 컴파일러는 "인라인" 요청을 무시하고 함수를 일반 함수로 취급할 수 있다.

 

 

 


 

 

먼저 함수 호출 원리 Check

 

 

 

 

 

 

10번 Line - main에서 한 줄 한줄 쭉 실행 중에 SetIndex 함수 만나서 함수를 호출 

-> 함수 위치로 명령 라인 넘어가서 쭉 실행하고

-> 다 끝나면 main에서 함수 실행 값 가지고 와서 적용

->11번 Line - 다음 라인부터 다시 쭉 실행

 

 

 


 

 

 

함수 호출 - 내부 로직 확인 - 메모리 상태 확인

 

 

 

Setindex( ) 함수 호출 과정

 

 

 

① CPU main 에있는 명령어들 하나하나 읽어가면서 실행

② 함수 호출 라인으로 넘어가기 전에 메모리에다가 그 전까지 했던 작업들의 값을 따로 저장

③ CPU에서 Setindex 함수 쭉 실행

④ main으로 다시 넘어가기전에 저장해놨던 값으로 세팅

⑤ 함수 했던 거 적용

⑥ main에서 다음 라인부터 실행

 

 

 

 


 

 

 

만약 for문 안에 함수가 있다면? 

 

 

for문 안으로 SetIndex ( ) 함수 넣어준 모습

 

 

 

for문으로 반복되는 동안 매번 SetIndex( )가 호출되는 것이고,

위의 ①②③④⑤ 번 과정이 계~속 반복되는 것이다. - 실행 시간 증가와 - 오버헤드 발생 가능성

 

이처럼 함수의 호출 비용이 크다는 것을 알 수 있다.

 

 

 

 


 

 

 

이러한 상황을 만들지 않기 위해 인라인 함수로 만들어달라고 요청

 

  • 함수는 호출되는 모든 위치에서 컴파일러가 해당 함수 정의를 대체할 수 있도록
  • 함수를 인라인으로 만들도록 컴파일러에 지시할 수 있다.  
  • 함수를 -> 인라인 함수로 만들도록 지시하는 방법이 - 함수를 클래스 내부에 선언해서 사용하는 것!

 

 

 

위에서 말한 -

클래스 내부에 함수를 선언하게 되면 inline 함수를 만들어 달라는 요청으로 받아들인다.

이 말의 의미를 이해할 수 있었다.

 

 

 

먼저 SetIndex( ) 함수 확인

 

 

 

 

 

클래스 내부에 함수를 만들면 inline 함수를 만들어달라 - 요청하는 의미로 받아들인다.

 

 

10번 라인 .SetIndex( ) 함수처럼 클래스 내부에 함수를 선언하면 - 인라인 함수로 적용해달라는 요청 -  2번으로 적용해달라는 요청

 

 

 

SetIndex 함수를 호출했을 경우

1번은 함수 호출로 적용했을 경우 -  적용 코드

2번은 인라인 함수로 적용 - 코드를 박아버렸을 때 - 적용 코드

 

 

 

 

정리 --

 

  • 이처럼 C++ 인라인 함수는 대안을 제공한다.
  • 인라인 키워드를 사용하여 - 컴파일러는 함수 호출 문을 함수 코드 자체로 대체한 다음 - 전체 코드를 컴파일합니다.
  • 컴파일러가 상태를 보고 코드박을 수 있으면 코드로 , 안될 거 같으면 함수 호출로 해준다.

 

 

 

 


 

 

 

review

이번 시간에는 파일 분할과 inline함수에 대하여 배웠다.

파일 분할이라는 개념이 신기하였다.
java에서 클래스 부분을 따로 만들고 main 있는 클래스를 또 만들어서
거기서 출력하는 방식과 비슷한 느낌이었다.

추가된 개념은
헤더 파일에는 선언부를, cpp파일에는 구현부를 나눠서 따로 관리하는 점이었다.
이렇게 관리하는 이유는 아무래도 기능 추가나 유지보수 측면에서 좋아서인 것 같다.
디버깅 관련해서는 아직 어떤 이점이 있는지 모르겠다. ㅎㅎ

 인라인 부분도 재미있었다.
함수를 호출하는데 저런 과정이 있는지 알 수 있어서 신기하였고
함수 호출 비용이 크다는 것도 알게 되었다.
한 두 번이야 별 차이가 없겠지만 이것이 반복되거나 처리량이 많아지면
0.01초가 100번만 쌓여도 1초. 천 번, 만 번이라면? 오우.
큰 관점에서 생각하는 습관 중요!

외부에 선언된 함수도 inline 키워드를 사용해 inline함수로 요청하는 부분도 있는데
그 부분은 더 check 해봐야겠다.