Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
포트폴리오에서 사용한
Modern C++
데브루키 쿵쾅(김광일)
STL(Standard Template Library)
C++에서 제공하는 자료구조, 알고리즘, 등을 사용하기 편하게 만든 표준 템플릿 라이브러리
std::vector, std::list, std::map, std::sort, std::for_each 등이 있다.
반복자(iterator)
STL 컨테이너의 내부 요소를 순회하는 객체로 포인터와 유사하나 포인터와는 다르다.
1 2 3 4 5std::vector<int> vec
std::vector<int>::iterator iter
vec.begin() vec.end()
auto 키워드
c++11
변수를 초기화하는 동시에 자료형을 추론해주는 키워드. 초기화를 하지 않을 경우 사용할 수 없다.
auto num; // error
auto num = 1; // int
auto num = 1.f // float
auto num = 1.0 // double
auto 키워드
c++11
vector<int> vec;
auto iter = vec.begin(); // vector<int>::iterator
위와 같이 긴 반복자 변수를 auto 키워드로 줄일 수 있다.
auto func = []() { std::cout << “Func” }; // 함수 포인터 타입
범위 기반 for문
c++11
배열 또는 컨테이너에 접근하여 모든 원소를 순회하는 for문
일반 배열에서는 범위기반이 동작하지만 동적 배열에는 동작하지 않는다.
int arr[10] = {}
for(int i = 0; i < 10; ++i)
std::cin >> arr[i];
int arr[10] = {}
for(int num : arr)
std::cin >> num;
범위 기반 for문
c++11
위와 같이 STL 컨테이너 순회에 응용할 수 있다.
std::vector<int> vec(10, 0);
auto iter = vec.begin();
auto iter_end = vec.end();
for( ; iter != iter_end; ++iter)
std::cin >> *iter;
std::vector<int> vec(10, 0);
for(auto& num : vec)
std::cin >> num;
std::vector<int> vec(10, 0);
for(size_t i = 0; i < vec.size(); ++i)
std::cin >> vec[i];
범위 기반 for문
c++11
override 키워드
c++11
class Parent
{
virtual void Out() { std::cout << “Parent”
<< std::endl;
}class Derived : public Parent
{
virtual void Out() override { std::cout << “Derived ”
<< std::endl;
void MyOut() override; // error
}
가상 함수를 자식 클래스에서 오버 라이딩 할 때
명시적으로 선언할 수 있다.
부모 클래스에서 선언되지 않은 함수를 명시적
으로 표기할 때 에러를 발생한다.
override 키워드
c++11
GameObject.h SpaceMarin.h
Uniform 초기화
c++11
모든 오브젝트 타입에 대해 동일한 방식으로 리스트형 초기화를 할 수 있는 기능.
int i = { 1 };
int arr[5] = { 1, 2, 3, 4, 5 };
std::vector<int> vec = { 1, 2, 3 };
struct Point
{
float a, float b;
}
Point pt = {};
Uniform 초기화
c++11
class Vector3
{
Vector3() {};
Vector3(float _x, float _y, float _z)
{ x = _x; y = _y; z = _z; }
float x, y, z;
}
Vector3 player_position = {};
Vector3 enemy_position = { 5.f, 0.f, 10.f };
다음과 같은 경우 player_position의 멤버 변수는 초기화 되
지 않음. 아무런 초기화도 하지 않는 생성자가 호출되었기
때문이다.
enemy_position의 경우 Vector3(float _x, float _y, float _z)
생성자를 호출하여 Uniform 초기화를 수행.
클래스의 생성 형식을 유지하고 싶다면 explicit 키워드를 사
용하자.
Uniform 초기화
c++11
명시적 기본 설정 및 삭제
c++11
Effective C++에서 복사가 일어나면 안되는 객체에 대하여 복사를 막는 방법으로 아래와 같이
복사생성자를 private로 하는 방법이 나와있다.
class Player
{
private:
Player(const Player& rhs);
};
명시적 기본 설정 및 삭제
c++11
C++11 부터는 클래스를 작성하면 기본적으로 만들어주는 생성자, 복사 생성자, 대입연산자를
명시적으로 선언하거나 삭제할 수 있다.
class Player
{
Player() = default;
Player(const Player& rhs) = delete;
};
명시적 기본 설정 및 삭제
c++11
enum vs enum class
c++11
enum ObjectTag { Player, Enemy, Environment, End };
enum ColliderTag { Enemy, Player, Environment, End };
std::list<CGameObject*> object_list_[End]; (o)
std::list<CGameObject*> object_list_[ObjectTag::End];
(o)
enum class ObjectTag { Player, Enemy, Environment, End };
enum class ColliderTag { Player, Enemy, Environment, End };
std::list<CGameObject*> object_list_[End]; (x)
std::list<CGameObject*> object_list_[ObjectTag::End]; (x)
C++에서는 enum의 값을 int 형태로 처리한다. 왼쪽과 같이 기존의 enum으로 선언할 경우 정적
배열의 enum 개수만큼 초기화 할 수 있다. 그리고 enum 타입을 생략하고 사용할 수 있다. 하지
만 ObjectTag 타입에 선언된 이름과 ColliderTag에 선언된 이름은 중복될 경우 타입을 명시적
으로 하지 않으면 ColliderTag의 값으로 인식한다.
enum vs enum class
c++11
enum ObjectTag { Player, Enemy, Environment, End };
enum ColliderTag { Enemy, Player, Environment, End };
std::list<CGameObject*> object_list_[End]; (o)
std::list<CGameObject*> object_list_[ObjectTag::End];
(o)
enum class ObjectTag { Player, Enemy, Environment, End };
enum class ColliderTag { Player, Enemy, Environment, End };
std::list<CGameObject*> object_list_[End]; (x)
std::list<CGameObject*> object_list_[ObjectTag::End]; (x)
오른쪽의 enum class의 경우 명시적으로 enum 타입을 사용해야 열거형 값을 사용할 수 있다.
단 unsigned int로 인식은 하지만 enum 타입때문에 정적 배열에 사용할 수 없다.
enum vs enum class
c++11
C++에서는 enum의 값을 int로 처리한다.
enum vs enum class
c++11
push_back vs emplace_back
c++11
push_back의 경우 기존의 STL 컨테이너에 원소를 넣을 때 사용하는 함수이다.
emplace_back는 C++11부터 추가된 STL 컨테이너에 원소를 넣을 때 사용하는 함수이다.
둘은 같은 역할을 한다. 하지만 차이점이 존재한다.
std::vector<Object*> vec_object; vec_object.push_back(new Object);
vec_object.emplace_back(new Object);
push_back vs emplace_back
c++11
int main()
{
std::vector<Object> vec;
vec.reserve(100);
std::cout << "push_back: "
<< std::endl;
vec.push_back(5);
std::cout << std::endl;
std::cout << "emplace_back: "
<< std::endl;
vec.emplace_back(5);
std::cout << std::endl;
return 0;
}
#include <iostream>
#include <vector>
class Object
{
public:
Object(int _hp) : hp(_hp) { std::cout << "생성자" << std::endl; }
Object(const Object& rhs) : hp(rhs.hp) { std::cout << "복사 생성자"
<< std::endl; }
~Object() { std::cout << "소멸자" << std::endl; }
private:
int hp = 0;
};
위 코드를 실행한다.
push_back vs emplace_back
c++11
int main()
{
std::vector<Object> vec;
vec.reserve(100);
std::cout << "push_back: "
<< std::endl;
vec.push_back(5);
std::cout << std::endl;
std::cout << "emplace_back: "
<< std::endl;
vec.emplace_back(5);
std::cout << std::endl;
return 0;
}
#include <iostream>
#include <vector>
class Object
{
public:
Object(int _hp) : hp(_hp) { std::cout << "생성자" << std::endl; }
Object(const Object& rhs) : hp(rhs.hp) { std::cout << "복사 생성자"
<< std::endl; }
~Object() { std::cout << "소멸자" << std::endl; }
private:
int hp = 0;
};
다음과 같은 결과를 나타낸다.
push_back vs emplace_back
c++11
int main()
{
std::vector<Object> vec;
vec.reserve(100);
std::cout << "push_back: "
<< std::endl;
vec.push_back(5);
std::cout << std::endl;
std::cout << "emplace_back: "
<< std::endl;
vec.emplace_back(5);
std::cout << std::endl;
return 0;
}
#include <iostream>
#include <vector>
class Object
{
public:
Object(int _hp) : hp(_hp) { std::cout << "생성자" << std::endl; }
Object(const Object& rhs) : hp(rhs.hp) { std::cout << "복사 생성자"
<< std::endl; }
~Object() { std::cout << "소멸자" << std::endl; }
private:
int hp = 0;
};
push_back과 emplace_back는 각각 &&(R-Value Reference)를 받는 함수로 오버로딩 되어있다.
이때 push_back의 경우 임시 객체를 생성하고 vector에 삽입 후 삭제까지한다. 반면
emplace_back의 경우 원소의 그 위치에 바로 생성하는 형태이다.
push_back vs emplace_back
c++11
중간에 원소를 삽입하는 Insert와 같은 역할을 하는 emplace 함수도 있다.
vector는 emplace_back, emplace
list는 emplace_front, emplace_back, emplace
map은 emplace 함수가 각각 구현되어있다.
R-Value Reference
c++11
int num = 1;
L-Value R-Value
먼저 L-Value와 R-Value를 알아본다.
위 변수와 같이 왼쪽에 있는 값이 L-Value, 오른쪽에 있는 값이 R-Value이다.
R-Value Reference
c++11
int num = 1;
L-Value R-Value
보통 왼쪽에는 변수를 선언할 것이고 오른쪽에는 다른 변수를 대입하거나 임의의 상수 또는 임시
객체 등을 사용할 것이다. 이때 임의의 상수 리터럴 변수와 임시 객체 등은 R-Value이다.
R-Value Reference
c++11
int num = 1;
int num2 = num;
위 상태의 경우 num은 오른쪽에 있지만 L-Value 이다.
R-Value Reference
c++11
int num = 5;
int 1 = num; (Error)
R-Value를 조금 더 쉽게 구분하자면 왼쪽에 올 수 없는 값(리터럴, 임시 객체 등은 왼쪽에 올 수 없
다)을 R-Value라고 생각하면 될 것이다.
class A {};
A a;
A() = a; (Error)
R-Value Reference
c++11
리터럴 상수 5를 num이라는 R-Value Reference에 초기화 하였다. 기존의 레퍼런스는 위와 같이
리터럴 상수 또는 임시 객체를 담을 수 없다.
int& r_num = 5; (Error)
int&& num = 5;
class A {};
A& a = A(); (Error)
A&& a = A();
Lambda
c++11
람다식은 무명 함수(이름 없는 함수)라고 부른다.
이름 그대로 함수의 이름이 존재하지 않는 함수이다.
[&](int a) -> int { return a; }
[ 캡처절 ] ( 매개변수 ) -> 반환형 { 구현부 }
Lambda
c++11
람다식 구현부에서는 람다식 외부의 변수 또는 함수를 사용할 수 없다. 이때 캡처절을 이용하여
외부 지역의 변수 또는 클래스의 멤버 함수를 가져와서 사용할 수 있다.
[&](int a) -> int { return a; }
[ 캡처절 ] ( 매개변수 ) -> 반환형 { 구현부 }
Lambda
c++11
[]: 공백일 때에는 아무 것도 캡처하지 않는 상태이다.
[=]: 외부 지역의 모든 변수와 클래스의 경우 멤버 함수 주소까지 값(Call by Value)으로 캡처한다.
[&]: 외부 지역의 모든 변수와 클래스의 경우 멤버 함수 주소까지 레퍼런스(Call by Reference)로 캡처한다.
[x]: 외부 지역의 변수 x를 값으로 캡처한다.
[&x]: 외부 지역의 변수 레퍼런스로 캡처한다.
[&](int a) -> int { return a; }
[ 캡처절 ] ( 매개변수 ) -> 반환형 { 구현부 }
Lambda
c++11
[x, &]: x만 값으로 그 외에는 레퍼런스로 캡처한다.
[&x, =]: x만 레퍼런스로 그 외에는 값으로 캡처한다.
[x, &y]: x는 값으로, y는 레퍼런스로 캡처한다.
[&](int a) -> int { return a; }
[ 캡처절 ] ( 매개변수 ) -> 반환형 { 구현부 }
Lambda
c++11
그 외에 매개변수절은 기존 함수와 동일하고 반환형의 경우 명시적으로 선언할 수 있고 따로 명
시하지 않아도 return 값에 따라 반환 타입을 결정해준다. 구현부에는 함수의 내용을 구현하면
된다.
[&](int a) -> int { return a; }
[ 캡처절 ] ( 매개변수 ) -> 반환형 { 구현부 }
Lambda
c++11
다음과 같이 STL 알고리즘 함수에 임의의 조건자를 넣을 때 람다식으로 만들어서 넣을 수 있다.
함수자 또는 함수객체를 대체할 수 있으며 함수 객체의 장점인 멤버 변수를 가질 수 있는 부분
을 캡처절을 활용하여 대체할 수 있다.
std::function
<functional>
c++11
함수 포인터를 유연하게 사용하기 위한 변수. 함수 포인터의 경우 람다, 함수 객체를 담을 수 없
다.
C++11 부터는 유연성이 강한 함수를 담을 수 있는 변수를 제공한다.
std::function<void()> func = []()->void { std::cout << “Hello World” << std::endl; };
std::function<int(int)> func = [](int a)->int { return a; };
std::function
<functional>
c++11
람다식 + std::function을 이용한 AI
random
<random>
c++11
seed값을 통하여 난수를 반환하는 rand 함수는 매우 한계가 크다.
C++11부터는 random 클래스를 제공하여 좀 더 확실한 난수를 얻을 수 있다.
random_device를 이용하면 하드웨어의 리소스를 사용하여 시드를 설정하여 랜덤값을 가져온
다.
std::random_device _random_device;
std::mt19937 _mt(_random_device());
std::uniform_int_distribution<int> range(min, max);
int random_num = range(_mt);
random
<random>
c++11
mt19937은 메르센 트위스터 엔진을 사용하여 난수를 추출한다. 장치에서 얻어온 시드값을 통
해 메르센 소수를 이용하여 난수 시드를 재설정한다. 이제 난수생성 형식과 타입을 결정한다.
정수형 난수: std::uniform_int_distribution
실수형 난수: std::uniform_real_distribution 외에도 다양한 난수 분포가 존재한다.
std::random_device _random_device;
std::mt19937 _mt(_random_device());
std::uniform_int_distribution<int> range(min, max);
int random_num = range(_mt);
random
<random>
c++11
오른쪽의 default_random_engine를 사용하면 랜덤 장치에 기본이 메르센 트위스터 엔진으로
설정되어 있는 상태에서 난수를 발생한다.
std::random_device _random_device;
std::mt19937 _mt(_random_device());
std::uniform_int_distribution<int> range(min, max);
int random_num = range(_mt);
random
c++11
filesystem
c++17
C++11에서는 Boost 라이브러리가 있어야 사용할 수 있다.
C++14부터는 std::experimental::filesystem으로 사용할 수 있고 C++17부터 표준이 되었다.
네임스페이스가 길기 때문에 위와 같이 사용할 수 있다.
#include <filesystem>
namespace FILESYSTEM = std::experimental::filesystem;
filesystem
c++17
파일 시스템은 기본적으로 path변수에 생성자로 넣어준 경로로 부터 시작합니다.
FILESYSTEM::path find_file(“..ResourceMesh”);
filesystem
c++17
파일 시스템은 기본적으로 path변수에 생성자로 넣어준 경로로 부터 시작한다.
filesystem에 탑재된 is_directory 함수를 통하여 현재 경로가 폴더인지 확인할 수 있다
FILESYSTEM::path find_file(“..ResourceMesh”);
if(false == FILESYSTEM::is_directory(find_file)) return;
filesystem
c++17
FILESYSTEM::directory_iterator을 통해 폴더 내의 모든 파일에 대해 접근할 수 있는 반복자를
사용합니다. 범위 기반 for문을 통해 각 파일을 FILESYSTEM::directory_entry라는 폴더 내 항목
변수에 담는다.
FILESYSTEM::path find_file(“..ResourceMesh”);
for(const auto& directory : FILESYSTEM::directory_iterator(find_file))
{
(. . .)
}
filesystem
c++17
FILESYSTEM::is_regular_file 함수를 통해 현재 directory의 상태(경로의 파일 상태)가 일반 파일인지 확인한
다.
for(const auto& directory : FILESYSTEM::directory_iterator(find_file))
{
if(FILESYSTEM::is_regular_file(directory.status()) { (. . .) }
}
filesystem
c++17
directory의 멤버 함수 path()를 사용하여 현재 경로를 반환한다. 반환형은 FILESYSTEM::path이기 때문에
문자열 첫 번째 주소를 반환하는 c_str() 멤버 함수를 통해 경로의 문자열을 얻을 수 있다. string에 넣을 경우
대입연산자에 넣으면 된다.
for(const auto& directory : FILESYSTEM::directory_iterator(find_file))
{
char filepath[128] = “”;
strcmp(filepath, directory.path().c_str());
string filepath = directory.path();
}
filesystem
c++17
path의 멤버 함수 extension()을 호출하면 .을 포함한 확장자만 얻을 수 있다. 이 확장자 역시 path 변수로 나
오며 멤버 함수 compare를 통해 문자열 비교를 할 수 있다. strcmp와 동일하게 0이 나오면 같은 문자열이
다.
확장자를 검사하여 그 확장자 파일만 로드하도록 할 수 있다.
for(const auto& directory : FILESYSTEM::directory_iterator(find_file))
{
if(0 == directory.path().extension().compare(“.png”)) { (. . .) }
}
filesystem
c++17
path의 멤버 함수 stem()을 호출하면 현재 경로의 파일 또는 폴더명만 남기고 경로, 확장자를 제거한 path를
반환한다. map 등에 저장할 key값으로 주로 사용하였다.
for(const auto& directory : FILESYSTEM::directory_iterator(find_file))
{
string key = directory.path().stem();
}
filesystem
c++17
vector 접근 팁
vector에 원소에 접근하는데 가장 왼쪽과 같은 []연산자를 통한 접근은 매우 비효율적이다.
가장 오른쪽과 같이 벡터 배열의 첫 번째 주소를 받아온 다음 주소를 건너뛰는 방식을 사용하는 것이 성능을
향상시키는데 매우 도움이 된다.
속도는 오른쪽 > 중간 > 왼쪽 순으로 오른쪽이 가장 빠르다. 포인터 형식이 불편하면 반복자 또는 범위기반
을 이용하는것이 차선책이다.
vector<int> vec = { 1, 2, 3, 4, 5 };
for(size_t i = 0; i < vec.size(); ++i)
vec[i] = 0;
vector<int> vec = { 1, 2, 3, 4, 5 };
int* vec_first = &vec[0];
for(size_t i = 0; i < vec.size(); ++i)
(*vec_first + i) = 0;
vector<int> vec = { 1, 2, 3, 4, 5 };
auto iter = vec.begin();
auto iter = vec.end();
for( ; iter != iter_end; ++iter)
*iter = 0;
Q & A

More Related Content

포트폴리오에서 사용한 모던 C++

  • 2. STL(Standard Template Library) C++에서 제공하는 자료구조, 알고리즘, 등을 사용하기 편하게 만든 표준 템플릿 라이브러리 std::vector, std::list, std::map, std::sort, std::for_each 등이 있다.
  • 3. 반복자(iterator) STL 컨테이너의 내부 요소를 순회하는 객체로 포인터와 유사하나 포인터와는 다르다. 1 2 3 4 5std::vector<int> vec std::vector<int>::iterator iter vec.begin() vec.end()
  • 4. auto 키워드 c++11 변수를 초기화하는 동시에 자료형을 추론해주는 키워드. 초기화를 하지 않을 경우 사용할 수 없다. auto num; // error auto num = 1; // int auto num = 1.f // float auto num = 1.0 // double
  • 5. auto 키워드 c++11 vector<int> vec; auto iter = vec.begin(); // vector<int>::iterator 위와 같이 긴 반복자 변수를 auto 키워드로 줄일 수 있다. auto func = []() { std::cout << “Func” }; // 함수 포인터 타입
  • 6. 범위 기반 for문 c++11 배열 또는 컨테이너에 접근하여 모든 원소를 순회하는 for문 일반 배열에서는 범위기반이 동작하지만 동적 배열에는 동작하지 않는다. int arr[10] = {} for(int i = 0; i < 10; ++i) std::cin >> arr[i]; int arr[10] = {} for(int num : arr) std::cin >> num;
  • 7. 범위 기반 for문 c++11 위와 같이 STL 컨테이너 순회에 응용할 수 있다. std::vector<int> vec(10, 0); auto iter = vec.begin(); auto iter_end = vec.end(); for( ; iter != iter_end; ++iter) std::cin >> *iter; std::vector<int> vec(10, 0); for(auto& num : vec) std::cin >> num; std::vector<int> vec(10, 0); for(size_t i = 0; i < vec.size(); ++i) std::cin >> vec[i];
  • 9. override 키워드 c++11 class Parent { virtual void Out() { std::cout << “Parent” << std::endl; }class Derived : public Parent { virtual void Out() override { std::cout << “Derived ” << std::endl; void MyOut() override; // error } 가상 함수를 자식 클래스에서 오버 라이딩 할 때 명시적으로 선언할 수 있다. 부모 클래스에서 선언되지 않은 함수를 명시적 으로 표기할 때 에러를 발생한다.
  • 11. Uniform 초기화 c++11 모든 오브젝트 타입에 대해 동일한 방식으로 리스트형 초기화를 할 수 있는 기능. int i = { 1 }; int arr[5] = { 1, 2, 3, 4, 5 }; std::vector<int> vec = { 1, 2, 3 }; struct Point { float a, float b; } Point pt = {};
  • 12. Uniform 초기화 c++11 class Vector3 { Vector3() {}; Vector3(float _x, float _y, float _z) { x = _x; y = _y; z = _z; } float x, y, z; } Vector3 player_position = {}; Vector3 enemy_position = { 5.f, 0.f, 10.f }; 다음과 같은 경우 player_position의 멤버 변수는 초기화 되 지 않음. 아무런 초기화도 하지 않는 생성자가 호출되었기 때문이다. enemy_position의 경우 Vector3(float _x, float _y, float _z) 생성자를 호출하여 Uniform 초기화를 수행. 클래스의 생성 형식을 유지하고 싶다면 explicit 키워드를 사 용하자.
  • 14. 명시적 기본 설정 및 삭제 c++11 Effective C++에서 복사가 일어나면 안되는 객체에 대하여 복사를 막는 방법으로 아래와 같이 복사생성자를 private로 하는 방법이 나와있다. class Player { private: Player(const Player& rhs); };
  • 15. 명시적 기본 설정 및 삭제 c++11 C++11 부터는 클래스를 작성하면 기본적으로 만들어주는 생성자, 복사 생성자, 대입연산자를 명시적으로 선언하거나 삭제할 수 있다. class Player { Player() = default; Player(const Player& rhs) = delete; };
  • 16. 명시적 기본 설정 및 삭제 c++11
  • 17. enum vs enum class c++11 enum ObjectTag { Player, Enemy, Environment, End }; enum ColliderTag { Enemy, Player, Environment, End }; std::list<CGameObject*> object_list_[End]; (o) std::list<CGameObject*> object_list_[ObjectTag::End]; (o) enum class ObjectTag { Player, Enemy, Environment, End }; enum class ColliderTag { Player, Enemy, Environment, End }; std::list<CGameObject*> object_list_[End]; (x) std::list<CGameObject*> object_list_[ObjectTag::End]; (x) C++에서는 enum의 값을 int 형태로 처리한다. 왼쪽과 같이 기존의 enum으로 선언할 경우 정적 배열의 enum 개수만큼 초기화 할 수 있다. 그리고 enum 타입을 생략하고 사용할 수 있다. 하지 만 ObjectTag 타입에 선언된 이름과 ColliderTag에 선언된 이름은 중복될 경우 타입을 명시적 으로 하지 않으면 ColliderTag의 값으로 인식한다.
  • 18. enum vs enum class c++11 enum ObjectTag { Player, Enemy, Environment, End }; enum ColliderTag { Enemy, Player, Environment, End }; std::list<CGameObject*> object_list_[End]; (o) std::list<CGameObject*> object_list_[ObjectTag::End]; (o) enum class ObjectTag { Player, Enemy, Environment, End }; enum class ColliderTag { Player, Enemy, Environment, End }; std::list<CGameObject*> object_list_[End]; (x) std::list<CGameObject*> object_list_[ObjectTag::End]; (x) 오른쪽의 enum class의 경우 명시적으로 enum 타입을 사용해야 열거형 값을 사용할 수 있다. 단 unsigned int로 인식은 하지만 enum 타입때문에 정적 배열에 사용할 수 없다.
  • 19. enum vs enum class c++11 C++에서는 enum의 값을 int로 처리한다.
  • 20. enum vs enum class c++11
  • 21. push_back vs emplace_back c++11 push_back의 경우 기존의 STL 컨테이너에 원소를 넣을 때 사용하는 함수이다. emplace_back는 C++11부터 추가된 STL 컨테이너에 원소를 넣을 때 사용하는 함수이다. 둘은 같은 역할을 한다. 하지만 차이점이 존재한다. std::vector<Object*> vec_object; vec_object.push_back(new Object); vec_object.emplace_back(new Object);
  • 22. push_back vs emplace_back c++11 int main() { std::vector<Object> vec; vec.reserve(100); std::cout << "push_back: " << std::endl; vec.push_back(5); std::cout << std::endl; std::cout << "emplace_back: " << std::endl; vec.emplace_back(5); std::cout << std::endl; return 0; } #include <iostream> #include <vector> class Object { public: Object(int _hp) : hp(_hp) { std::cout << "생성자" << std::endl; } Object(const Object& rhs) : hp(rhs.hp) { std::cout << "복사 생성자" << std::endl; } ~Object() { std::cout << "소멸자" << std::endl; } private: int hp = 0; }; 위 코드를 실행한다.
  • 23. push_back vs emplace_back c++11 int main() { std::vector<Object> vec; vec.reserve(100); std::cout << "push_back: " << std::endl; vec.push_back(5); std::cout << std::endl; std::cout << "emplace_back: " << std::endl; vec.emplace_back(5); std::cout << std::endl; return 0; } #include <iostream> #include <vector> class Object { public: Object(int _hp) : hp(_hp) { std::cout << "생성자" << std::endl; } Object(const Object& rhs) : hp(rhs.hp) { std::cout << "복사 생성자" << std::endl; } ~Object() { std::cout << "소멸자" << std::endl; } private: int hp = 0; }; 다음과 같은 결과를 나타낸다.
  • 24. push_back vs emplace_back c++11 int main() { std::vector<Object> vec; vec.reserve(100); std::cout << "push_back: " << std::endl; vec.push_back(5); std::cout << std::endl; std::cout << "emplace_back: " << std::endl; vec.emplace_back(5); std::cout << std::endl; return 0; } #include <iostream> #include <vector> class Object { public: Object(int _hp) : hp(_hp) { std::cout << "생성자" << std::endl; } Object(const Object& rhs) : hp(rhs.hp) { std::cout << "복사 생성자" << std::endl; } ~Object() { std::cout << "소멸자" << std::endl; } private: int hp = 0; }; push_back과 emplace_back는 각각 &&(R-Value Reference)를 받는 함수로 오버로딩 되어있다. 이때 push_back의 경우 임시 객체를 생성하고 vector에 삽입 후 삭제까지한다. 반면 emplace_back의 경우 원소의 그 위치에 바로 생성하는 형태이다.
  • 25. push_back vs emplace_back c++11 중간에 원소를 삽입하는 Insert와 같은 역할을 하는 emplace 함수도 있다. vector는 emplace_back, emplace list는 emplace_front, emplace_back, emplace map은 emplace 함수가 각각 구현되어있다.
  • 26. R-Value Reference c++11 int num = 1; L-Value R-Value 먼저 L-Value와 R-Value를 알아본다. 위 변수와 같이 왼쪽에 있는 값이 L-Value, 오른쪽에 있는 값이 R-Value이다.
  • 27. R-Value Reference c++11 int num = 1; L-Value R-Value 보통 왼쪽에는 변수를 선언할 것이고 오른쪽에는 다른 변수를 대입하거나 임의의 상수 또는 임시 객체 등을 사용할 것이다. 이때 임의의 상수 리터럴 변수와 임시 객체 등은 R-Value이다.
  • 28. R-Value Reference c++11 int num = 1; int num2 = num; 위 상태의 경우 num은 오른쪽에 있지만 L-Value 이다.
  • 29. R-Value Reference c++11 int num = 5; int 1 = num; (Error) R-Value를 조금 더 쉽게 구분하자면 왼쪽에 올 수 없는 값(리터럴, 임시 객체 등은 왼쪽에 올 수 없 다)을 R-Value라고 생각하면 될 것이다. class A {}; A a; A() = a; (Error)
  • 30. R-Value Reference c++11 리터럴 상수 5를 num이라는 R-Value Reference에 초기화 하였다. 기존의 레퍼런스는 위와 같이 리터럴 상수 또는 임시 객체를 담을 수 없다. int& r_num = 5; (Error) int&& num = 5; class A {}; A& a = A(); (Error) A&& a = A();
  • 31. Lambda c++11 람다식은 무명 함수(이름 없는 함수)라고 부른다. 이름 그대로 함수의 이름이 존재하지 않는 함수이다. [&](int a) -> int { return a; } [ 캡처절 ] ( 매개변수 ) -> 반환형 { 구현부 }
  • 32. Lambda c++11 람다식 구현부에서는 람다식 외부의 변수 또는 함수를 사용할 수 없다. 이때 캡처절을 이용하여 외부 지역의 변수 또는 클래스의 멤버 함수를 가져와서 사용할 수 있다. [&](int a) -> int { return a; } [ 캡처절 ] ( 매개변수 ) -> 반환형 { 구현부 }
  • 33. Lambda c++11 []: 공백일 때에는 아무 것도 캡처하지 않는 상태이다. [=]: 외부 지역의 모든 변수와 클래스의 경우 멤버 함수 주소까지 값(Call by Value)으로 캡처한다. [&]: 외부 지역의 모든 변수와 클래스의 경우 멤버 함수 주소까지 레퍼런스(Call by Reference)로 캡처한다. [x]: 외부 지역의 변수 x를 값으로 캡처한다. [&x]: 외부 지역의 변수 레퍼런스로 캡처한다. [&](int a) -> int { return a; } [ 캡처절 ] ( 매개변수 ) -> 반환형 { 구현부 }
  • 34. Lambda c++11 [x, &]: x만 값으로 그 외에는 레퍼런스로 캡처한다. [&x, =]: x만 레퍼런스로 그 외에는 값으로 캡처한다. [x, &y]: x는 값으로, y는 레퍼런스로 캡처한다. [&](int a) -> int { return a; } [ 캡처절 ] ( 매개변수 ) -> 반환형 { 구현부 }
  • 35. Lambda c++11 그 외에 매개변수절은 기존 함수와 동일하고 반환형의 경우 명시적으로 선언할 수 있고 따로 명 시하지 않아도 return 값에 따라 반환 타입을 결정해준다. 구현부에는 함수의 내용을 구현하면 된다. [&](int a) -> int { return a; } [ 캡처절 ] ( 매개변수 ) -> 반환형 { 구현부 }
  • 36. Lambda c++11 다음과 같이 STL 알고리즘 함수에 임의의 조건자를 넣을 때 람다식으로 만들어서 넣을 수 있다. 함수자 또는 함수객체를 대체할 수 있으며 함수 객체의 장점인 멤버 변수를 가질 수 있는 부분 을 캡처절을 활용하여 대체할 수 있다.
  • 37. std::function <functional> c++11 함수 포인터를 유연하게 사용하기 위한 변수. 함수 포인터의 경우 람다, 함수 객체를 담을 수 없 다. C++11 부터는 유연성이 강한 함수를 담을 수 있는 변수를 제공한다. std::function<void()> func = []()->void { std::cout << “Hello World” << std::endl; }; std::function<int(int)> func = [](int a)->int { return a; };
  • 39. random <random> c++11 seed값을 통하여 난수를 반환하는 rand 함수는 매우 한계가 크다. C++11부터는 random 클래스를 제공하여 좀 더 확실한 난수를 얻을 수 있다. random_device를 이용하면 하드웨어의 리소스를 사용하여 시드를 설정하여 랜덤값을 가져온 다. std::random_device _random_device; std::mt19937 _mt(_random_device()); std::uniform_int_distribution<int> range(min, max); int random_num = range(_mt);
  • 40. random <random> c++11 mt19937은 메르센 트위스터 엔진을 사용하여 난수를 추출한다. 장치에서 얻어온 시드값을 통 해 메르센 소수를 이용하여 난수 시드를 재설정한다. 이제 난수생성 형식과 타입을 결정한다. 정수형 난수: std::uniform_int_distribution 실수형 난수: std::uniform_real_distribution 외에도 다양한 난수 분포가 존재한다. std::random_device _random_device; std::mt19937 _mt(_random_device()); std::uniform_int_distribution<int> range(min, max); int random_num = range(_mt);
  • 41. random <random> c++11 오른쪽의 default_random_engine를 사용하면 랜덤 장치에 기본이 메르센 트위스터 엔진으로 설정되어 있는 상태에서 난수를 발생한다. std::random_device _random_device; std::mt19937 _mt(_random_device()); std::uniform_int_distribution<int> range(min, max); int random_num = range(_mt);
  • 43. filesystem c++17 C++11에서는 Boost 라이브러리가 있어야 사용할 수 있다. C++14부터는 std::experimental::filesystem으로 사용할 수 있고 C++17부터 표준이 되었다. 네임스페이스가 길기 때문에 위와 같이 사용할 수 있다. #include <filesystem> namespace FILESYSTEM = std::experimental::filesystem;
  • 44. filesystem c++17 파일 시스템은 기본적으로 path변수에 생성자로 넣어준 경로로 부터 시작합니다. FILESYSTEM::path find_file(“..ResourceMesh”);
  • 45. filesystem c++17 파일 시스템은 기본적으로 path변수에 생성자로 넣어준 경로로 부터 시작한다. filesystem에 탑재된 is_directory 함수를 통하여 현재 경로가 폴더인지 확인할 수 있다 FILESYSTEM::path find_file(“..ResourceMesh”); if(false == FILESYSTEM::is_directory(find_file)) return;
  • 46. filesystem c++17 FILESYSTEM::directory_iterator을 통해 폴더 내의 모든 파일에 대해 접근할 수 있는 반복자를 사용합니다. 범위 기반 for문을 통해 각 파일을 FILESYSTEM::directory_entry라는 폴더 내 항목 변수에 담는다. FILESYSTEM::path find_file(“..ResourceMesh”); for(const auto& directory : FILESYSTEM::directory_iterator(find_file)) { (. . .) }
  • 47. filesystem c++17 FILESYSTEM::is_regular_file 함수를 통해 현재 directory의 상태(경로의 파일 상태)가 일반 파일인지 확인한 다. for(const auto& directory : FILESYSTEM::directory_iterator(find_file)) { if(FILESYSTEM::is_regular_file(directory.status()) { (. . .) } }
  • 48. filesystem c++17 directory의 멤버 함수 path()를 사용하여 현재 경로를 반환한다. 반환형은 FILESYSTEM::path이기 때문에 문자열 첫 번째 주소를 반환하는 c_str() 멤버 함수를 통해 경로의 문자열을 얻을 수 있다. string에 넣을 경우 대입연산자에 넣으면 된다. for(const auto& directory : FILESYSTEM::directory_iterator(find_file)) { char filepath[128] = “”; strcmp(filepath, directory.path().c_str()); string filepath = directory.path(); }
  • 49. filesystem c++17 path의 멤버 함수 extension()을 호출하면 .을 포함한 확장자만 얻을 수 있다. 이 확장자 역시 path 변수로 나 오며 멤버 함수 compare를 통해 문자열 비교를 할 수 있다. strcmp와 동일하게 0이 나오면 같은 문자열이 다. 확장자를 검사하여 그 확장자 파일만 로드하도록 할 수 있다. for(const auto& directory : FILESYSTEM::directory_iterator(find_file)) { if(0 == directory.path().extension().compare(“.png”)) { (. . .) } }
  • 50. filesystem c++17 path의 멤버 함수 stem()을 호출하면 현재 경로의 파일 또는 폴더명만 남기고 경로, 확장자를 제거한 path를 반환한다. map 등에 저장할 key값으로 주로 사용하였다. for(const auto& directory : FILESYSTEM::directory_iterator(find_file)) { string key = directory.path().stem(); }
  • 52. vector 접근 팁 vector에 원소에 접근하는데 가장 왼쪽과 같은 []연산자를 통한 접근은 매우 비효율적이다. 가장 오른쪽과 같이 벡터 배열의 첫 번째 주소를 받아온 다음 주소를 건너뛰는 방식을 사용하는 것이 성능을 향상시키는데 매우 도움이 된다. 속도는 오른쪽 > 중간 > 왼쪽 순으로 오른쪽이 가장 빠르다. 포인터 형식이 불편하면 반복자 또는 범위기반 을 이용하는것이 차선책이다. vector<int> vec = { 1, 2, 3, 4, 5 }; for(size_t i = 0; i < vec.size(); ++i) vec[i] = 0; vector<int> vec = { 1, 2, 3, 4, 5 }; int* vec_first = &vec[0]; for(size_t i = 0; i < vec.size(); ++i) (*vec_first + i) = 0; vector<int> vec = { 1, 2, 3, 4, 5 }; auto iter = vec.begin(); auto iter = vec.end(); for( ; iter != iter_end; ++iter) *iter = 0;
  • 53. Q & A