티스토리 뷰
유니폼 초기화
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 개 이상이므로 컴파일 오류
씹어먹는 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++는 컴파일 타임 연산 기능을 계속 확장하고 있음...
'개발 > C++' 카테고리의 다른 글
[ 씹어먹는 C++ ] 16~17 (0) | 2024.11.17 |
---|---|
[ 씹어먹는 C++ ] 쓰레드 풀 만들기 (0) | 2024.10.26 |
[ 씹어먹는 C++ ] C++의 멀티스레딩 (0) | 2024.10.13 |
[ 씹어먹는 C++ ] 함수 객체 (0) | 2024.10.12 |
[ 씹어먹는 C++ ] 스마트 포인터 (unique_ptr/ shared_ptr/ weak_ptr) (0) | 2024.09.22 |
- Total
- Today
- Yesterday
- dependencyResilutionManagement
- 바이너리세마포
- 광유다
- C++
- unorderedset
- photon
- SpinLock
- ARface
- unityAR
- 유니티
- Vector
- unorderedmap
- 동기화
- list
- NotFoundException: String resource ID #0x0
- 지크슈
- 안드로이드스튜디오
- Java
- 스핀락
- mutex
- 세마포
- 유니티슈팅게임
- 게임개발
- map
- 포톤
- StartActivityForResult
- registerForActivityResult
- Unity
- semaphore
- 뮤텍스
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |