내일배움캠프/TIL

TIL260508 - CPP

옆집히드라 2026. 5. 8. 12:22

1. 핵심 개념


  • const, constexpr, consteval(cpp20)
  • reference 타입
  • 함수 오버로딩 우선 순위: 정확한 매칭 -> 타입 승격 반환 -> 표준 타입 반환 -> 사용자 정의 타입 변환
    • 승격: 값이 손실나지 않는 방향으로 타입 캐스팅
  • 템플릿 문법(공변, 반공변성? 타입제약?, 구현은 컴파일 타임?)
  • STL 사용법(vector, map, container, algorithm, iterator); 언제 한 번 정리하기 + sort의 comp(a, b)
  • explicit는 컴파일러가 원하지 않는 암시적 형변환을 수행하지 못하도록 제한하는 키워드; 인자가 하나인 생성자에는 기본적으로 붙이는 것을 권장함(매개변수에 클래스를 받을 때 생성자에 따라서 자동으로 사용자 정의 타입으로 캐스팅해서 사용되는 경우가 있음)

2. 상세 내용


2.0. NRVO

객체를 리턴해야 하는 메서드를 구현할 때 굳이 call by reference를 사용하여 객체를 할당하지 않아도 컴파일러가 불필요한 복사를 막기 위해 최적화(NRVO)를 해준다.

#include <iostream>

class BigData {
public:
    BigData() { std::cout << "Default Constructor\n"; }
    BigData(const BigData&) { std::cout << "Copy Constructor (Expensive!)\n"; }
    BigData(BigData&&) noexcept { std::cout << "Move Constructor\n"; }

    // 이동 생성자가 명시적으로 선언되었기 때문에, 
    // 컴파일러가 안전을 위해 기본 복사/이동 대입 연산자의 생성을 암묵적으로 삭제해서 테스트를 위해 재정의
    BigData& operator=(const BigData&) {
        std::cout << "Copy Assignment\n";
        return *this;
    }

    BigData& operator=(BigData&&) noexcept {
        std::cout << "Move Assignment\n";
        return *this;
    }
};

// 1. 기존의 Out-parameter 방식
void createData_ByRef(BigData& outTarget) {
    std::cout << "  [Inside createData_ByRef]\n";
    outTarget = BigData(); // 임시 객체 대입 -> Move Assignment 호출
}

// 2. 권장되는 Return by Value 방식 (RVO/NRVO)
BigData createData_ByValue() {
    std::cout << "  [Inside createData_ByValue]\n";
    BigData temp;
    return temp; // RVO 적용 시 복사/이동 생략
}

int main() {
    std::cout << "--- 1. Out-parameter Test ---\n";
    BigData target; // Default Constructor
    createData_ByRef(target);

    std::cout << "\n--- 2. Return by Value Test ---\n";
    BigData result = createData_ByValue(); // Default Constructor (RVO 적용됨)

    return 0;
}

// 예상 출력
// Default Constructor
// [Inside createData_ByRef]
// Default Constructor
// Move Assignment
//
// -- - 2. Return by Value Test-- -
// [Inside createData_ByValue]
// Default Constructor

출력 결과를 보면 리턴값으로 객체를 반환하는 메서드에서 컴파일러가 NRVO(Named Return Value Optimization)를 해서 결과적으로 call by reference로 할당한 쪽보다 더 효율적이게 객체를 할당 받았다.

2.1. const

constant로 프로그램 실행 중 값이 변경되지 않도록 대상을 선언할 때 사용하는 키워드다.

상수로써 사용

const int versionNumberMajor { 1 };

cpp에서는 #define 대신에 const를 붙여 상수로 사용한다.

const pointer

const가 붙는 위치에 따라 변수가 지닌 메모리 주소가 상수인지, 변수가 가리키는 값이 상수인지 지정할 수 있다.

const를 읽는 방법은 기본적으로 오른쪽에서 왼쪽으로 읽으면 된다. [[TIL260427]]에서 다룬 Right-Left Rule을 기억하면서 변수를 읽어보자.

int a = 5;
int b = 7;

// ptr1, 2 is pointer to constant integer
int const * ptr1 { &a };
const int* ptr2 { &a }; // 이렇게 왼쪽에 쓰일 수도 있으니 해석의 주의하자.
// *ptr1 = 1; // 컴파일 에러

// ptr3 is constant pointer to integer
int* const ptr3 { &a };
// ptr3 = &b; // 컴파일 에러

// ptr4 is constant pointer to constant integer
const int* const ptr4 { &a };
// *ptr4 = 1; // 컴파일 에러 
// ptr4 = &b; // 컴파일 에러

const parameter

cpp에서는 비 const 변수를 const 변수로 캐스팅할 수 있다. const 매개변수를 받도록 지정하면 전달 받은 매개변수를 변경하려 할 때 컴파일 에러가 발생한다.

void test(const std::string* someString){
    *someString = "test"; // 컴파일 에러 발생
}

const method

const는 클래스 메서드에도 지정이 가능하다. const가 지정된 메서드는 해당 클래스의 데이터 멤버를 수정할 수 없다.

class a{
public:
    void someInspector() const; // 내부에서 a 같은 데이터 멤버 수정하려 할 때 컴파일 에러
private:
    int a;
}

[!NOTE] const-correctness principle
객체의 데이터 멤버를 변경하지 않는 멤버 함수는 const로 선언하는 것이 좋다. 이런 멤버 함수를 'inspector'라고 부른다.(비 conste 멤버 함수는 'mutator'라고 부름)

constexpr

함수에 붙일 수 있는 키워드로 constexpr 키워드를 붙이면 컴파일 시간에 평가될 수 있다. constexpr이 붙은 함수는 컴파일 시간에 실행될 수 있어야 하기 때문에 제약 사항이 붙는다. 비 constexpr 함수 호출 불가능하고 side effect가 없는 함수여야 한다.(exception 불가)

consteval

앞선 constexpr은 런타임 시간에도 호출이 될 수 있다. 하지만 consteval 키워드를 사용하면 해당 함수를 immediate function으로 만들어 반드시 컴파일 시간에 실행하도록 한다.

2.2. reference type(&)

cpp에서 레퍼런스(referece)는 변수에 대한 별칭(alias)이다. 초기화하지 않으면 컴파일 에러가 발생하며, 한 번 설정되었다면 변경이 불가능하다. 그리고 레퍼런스에 대한 레퍼런스를 설정하는 것도 불가능하다.

레퍼런스를 설정할 때는 int& aRef = &a;와 같이 참조 연산자로로 넘겨주지 않고 int& aRef = a;와 같이 넘겨주면 된다.

레퍼런스도 const 키워드를 붙일 수 있다. 또한 포인터에 대한 레퍼런스도 설정 가능하다.

int a = 5;
const int& aRef { a }; // aRef is reference to constant integer

int* aPtr = &a;
int*& aPtrRef = aPtr; // aPtrRef is reference to pointer to integer

주의할 점은 레퍼런스는 포인터와 타입이 다르기 때문에 타입 매칭 시 컴파일 에러가 발생할 수 있다.

int x = 3;
int& xRef { x };
int* xPtr { &x };
return xRef == xPtr // 컴파일 에러! xPtr == &xRef 같이 사용해야 함

2.3. Move semantics

3. 질문 및 해결 (Q&A)


  • 함수 시그니처 까먹음; 그리고 왜 반환타입은 오버로딩에 영향 x?

4. 관련 문서 (Links)


'내일배움캠프 > TIL' 카테고리의 다른 글

TIL260512 - Unreal  (0) 2026.05.12
TIL260511 - Unreal  (0) 2026.05.11
TIL260507 - Quaternion  (0) 2026.05.07
TIL260506 - Quaternion  (0) 2026.05.06
TIL260504 - Quaternion  (0) 2026.05.04