티스토리 뷰

유니폼 초기화

 

1. 배경

 

C++에서 객체를 생성할 때 괄호()를 사용하면 의도치 않게 함수 선언으로 해석되는 경우가 있다.

#include <iostream>

class A {
 public:
  A() { std::cout << "A 의 생성자 호출!" << std::endl; }
};

int main() {
  A a();  // 의도: A 객체 생성, 실제: 함수 선언!
}

 

이 코드는 A 객체를 생성하는 것처럼 보이지만, 실제로는 A를 반환하고 인자가 없는 함수 a를 선언한 것으로 해석된다.

 

2. 균일한 초기화 문법: 중괄호{} 사용

 

C++11에서는 이러한 모호성을 해결하기 위해 중괄호{}를 사용하는 균일한 초기화를 도입했다

#include <iostream>

class A {
 public:
  A() { std::cout << "A 의 생성자 호출!" << std::endl; }
};

int main() {
  A a{};  // 확실하게 객체 생성!
}

 

중괄호 초기화의 중요한 특징은 데이터 손실이 있는(Narrowing) 변환을 허용하지 않는다는 것이다.

#include <iostream>

class A {
 public:
  A(int x) { std::cout << "A 의 생성자 호출!" << std::endl; }
};

int main() {
  A a(3.5);  // 허용됨 - double에서 int로 암시적 변환
  A b{3.5};  // 컴파일 에러! - narrowing conversion 불허
}

 

3. 초기화 리스트 (Initializer List)

 

C++11에서는 initializer_list를 통해 컨테이너를 더 직관적으로 초기화할 수 있게 되었다

#include <iostream>
#include <map>
#include <string>
#include <vector>

template <typename T>
void print_vec(const std::vector<T>& vec) {
  std::cout << "[";
  for (const auto& e : vec) {
    std::cout << e << " ";
  }
  std::cout << "]" << std::endl;
}

template <typename K, typename V>
void print_map(const std::map<K, V>& m) {
  for (const auto& kv : m) {
    std::cout << kv.first << " : " << kv.second << std::endl;
  }
}

int main() {
  std::vector<int> v = {1, 2, 3, 4, 5};
  print_vec(v);

  std::map<std::string, int> m = {
    {"abc", 1}, {"hi", 3}, {"hello", 5}, {"c++", 2}, {"java", 6}};
  print_map(m);
}

 

 

initializer_list 사용 시 주의사항

initializer_list를 받는 생성자가 있다면, 중괄호 초기화 시 이 생성자가 우선적으로 고려된다.

#include <initializer_list>
#include <iostream>

class A {
 public:
  A(int x, double y) { std::cout << "일반 생성자! " << std::endl; }

  A(std::initializer_list<int> lst) {
    std::cout << "초기화자 사용 생성자! " << std::endl;
  }
};

int main() {
  A a(3, 1.5);  // 일반 생성자 호출
  A b{3, 1.5};  // 컴파일 에러! - double을 int로 변환 시도
}

 

 

4. auto와 initializer_list

 

C++17 이전과 이후에 auto와 중괄호 초기화의 동작이 다르다.

// C++17 이전

auto a = {1};     // std::initializer_list<int>
auto b{1};        // std::initializer_list<int>
auto c = {1, 2};  // std::initializer_list<int>
auto d{1, 2};     // std::initializer_list<int>

 

// C++17 이후

auto a = {1};     // 첫 번째 형태이므로 std::initializer_list<int>
auto b{1};        // 두 번째 형태 이므로 그냥 int
auto c = {1, 2};  // 첫 번째 형태이므로 std::initializer_list<int>
auto d{1, 2};  // 두 번째 형태 인데 인자가 2 개 이상이므로 컴파일 오류

 

 


 

 

https://modoocode.com/293

 

씹어먹는 C++ - <16 - 2. constexpr 와 함께라면 컴파일 타임 상수는 문제없어>

constexpr 을 통해 컴파일 타임 상수인 객체를 선언할 수 있다. const 와 constexpr 은 다르다. const 는 컴파일 타임에 상수일 필요가 없다! (const 인 애들 중에서 constexpr 이 있다고 생각하면 된다) constexpr

modoocode.com

 

constexpr - 컴파일 타임 상수

 

1. 컴파일 타임 상수가 필요한 경우

 

컴파일러가 컴파일 타임에 값을 결정할 수 있는 식을 상수식(Constant expression)이라고 한다. 특히 정수 상수식은 다음과 같은 상황에서 필요하다

 

- 배열 크기 선언

int arr[size];  // size는 정수 상수식이어야 함

 

- 템플릿 인자

template <int N>
struct A {
  int operator()() { return N; }
};
A<number> a;  // number는 정수 상수식이어야 함

 

- enum 값 지정

enum A { a = number, b, c };  // number는 정수 상수식이어야 함

 

 

2. constexpr vs const

const와 constexpr의 가장 큰 차이점은 컴파일 타임 계산 여부이다.

int a;
// 어떤 작업들...
const int b = a;      // OK - 런타임에 값이 결정됨
constexpr int c = a;  // 컴파일 에러! - 컴파일 타임에 값을 알 수 없음

 

정리하자면..

 

  • constexpr은 항상 const이지만
  • const는 constexpr이 아닐 수 있다

 

3. constexpr 함수

컴파일 타임 상수를 만들어내는 함수를 정의할 수 있다.

#include <iostream>

constexpr int Factorial(int n) {
  int total = 1;
  for (int i = 1; i <= n; i++) {
    total *= i;
  }
  return total;
}

template <int N>
struct A {
  int operator()() { return N; }
};

int main() {
  A<Factorial(10)> a;  // 컴파일 타임에 계산됨!
  std::cout << a() << std::endl;
}

 

 

C++14부터는 constexpr 함수 내부에서 다음을 제외한 대부분의 작업이 가능해졌다:

  • goto 문 사용
  • 예외 처리 (C++20부터 가능)
  • 리터럴 타입이 아닌 변수의 정의
  • 초기화되지 않은 변수의 정의
  • constexpr이 아닌 함수 호출

 

4. constexpr 생성자

클래스도 constexpr 객체를 만들 수 있다

class Vector {
 public:
  constexpr Vector(int x, int y) : x_(x), y_(y) {}
  constexpr int x() const { return x_; }
  constexpr int y() const { return y_; }

 private:
  int x_;
  int y_;
};

constexpr Vector AddVec(const Vector& v1, const Vector& v2) {
  return {v1.x() + v2.x(), v1.y() + v2.y()};
}

int main() {
  constexpr Vector v1{1, 2};
  constexpr Vector v2{2, 3};
  
  // 컴파일 타임에 계산됨
  A<AddVec(v1, v2).x()> b;
  std::cout << b() << std::endl;
}

 

 

5. if constexpr

C++17에서는 컴파일 타임 조건문인 if constexpr이 추가되었다.

template <typename T>
void show_value(T t) {
  if constexpr (std::is_pointer_v<T>) {
    std::cout << "포인터 이다 : " << *t << std::endl;
  } else {
    std::cout << "포인터가 아니다 : " << t << std::endl;
  }
}

 

if constexpr의 조건이 거짓이면 else 부분만 컴파일되고, 참이면 if 부분만 컴파일된다. 덕분에 컴파일되지 않는 부분의 코드는 검사하지 않는다.

 

C++는 컴파일 타임 연산 기능을 계속 확장하고 있음...

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함