C++ 고급 문법 테크닉 - C++ 11 14 기본 문법[3]
C++11/14 에서 추가된 다양한 문법을 빠르게 소개합니다.
Using
- c++11부터 추가됨, C의 typedef와 같은 역할을 함
- using은 template class에도 별칭을 붙일 수 있음 (typedef는 only type 대상)
template<typename T> struct Point
{
T x, y;
};
typedef Point Pixel; // ??? error
template<typename T>
using Pixel = Point<T>;
//typedef int DWORD;
//typedef void(*FP)(int);
using DWORD = int;
using FP = void(*)(int);
int main()
{
DWORD n; // int
FP p; // void(*p)(int)
Point<int> p1;
Pixel<int> p2; // Point<int> p2
}
-
활용 예제
#include <type_traits> #include <set> using namespace std; // 활용 1. type alias using DWORD = int; // 활용 2. template alias template<typename T, typename U> using Duo = pair<T, U>; Duo<int, double> d1; // pair<int, double> template<typename T> using Ptr = T*; Ptr<int> p2; // int* // 활용 3. 템플릿의 인자를 고정해서 사용 template<typename T> using Point = pair<T, T>; Point<int> p; // pair<int, int> // 활용 4. 복잡한 표현식을 축약해서 사용 template<typename T> using remove_pointer_t = typename remove_pointer<T>::type; template<typename T> void foo(T a) { // T에서 포인터를 제거한 타입을 구하고 싶다. //typename remove_pointer<T>::type t; // C++11 remove_pointer_t<T> t; // C++14 style }
Static_assert
- assert : 실행 시간에 표현식 조사
- static_assert : 컴파일 시간에 표현식 조사 (C++17부터 전달 메세지 생략 가능)
#include <iostream>
#include <cassert>
using namespace std;
void foo(int age)
{
assert(age > 0);
static_assert(age > 0, "error");
// ......
}
int main()
{
static_assert(sizeof(void*) >= 8,"error. 32bit");
static_assert(sizeof(void*) >= 16); // C++17
foo(-10);
}
- 활용 예제
#include <iostream>
#include <mutex>
using namespace std;
//#pragma pack(1) // 주석을 해제 할 경우 컴파일러의 패딩을 막아줌
struct PACKET
{
char cmd;
int data;
};
// 설계된 패킷에 불필요한 패딩이 발생 할 경우 컴파일 시간에 오류 발생 시키기
static_assert(
sizeof(PACKET) == sizeof(char) + sizeof(int),
"error, unexpected padding !");
template<typename T> void Swap(T& a, T& b)
{
// T가 가져야하는 조건을 조사 (T가 복사 생성 불가 할 경우 컴파일 오류 발생 시키기)
static_assert(is_copy_constructible<T>::value,
"error. T is not copyable");
T tmp = a;
a = b;
b = tmp;
}
int main()
{
mutex m1;
mutex m2;
Swap(m1, m2);
}
Begin / End
배열과 STL 컨테이너 모두를 순회(iteration) 할 수 있는 함수 작성 가능 대부분의 컴파일러가 헤더를 포함하지 않더라도 사용 할 수 있도록 지원하지만, 포함시켜 사용 하는 것이 표준
#include <iostream>
#include <list>
#include <vector>
#include <iterator>
using namespace std;
// 컨테이너의 모든 요소를 출력하는 함수.
template<typename T> void show(T& c)
{
// C++98/03 스타일
// auto p1 = c.begin();
// auto p2 = c.end();
auto p1 = begin(c);
auto p2 = end(c);
while (p1 != p2)
{
cout << *p1 << endl;
++p1;
}
}
int main()
{
list<int> c = { 1,2,3 };
//vector<int> c = { 1,2,3 };
show(c);
int x[3] = { 1,2, 3 };
show(x);
}
- begin, end의 원리 (template)
#include <iostream>
#include <list>
#include <vector>
using namespace std;
// container version.
template<typename C>
constexpr auto begin(C& c) -> decltype(c.begin())
{
return c.begin();
}
template<typename C>
constexpr auto end(C& c) -> decltype(c.end())
{
return c.end();
}
// arr version
template<typename T, std::size_t N>
constexpr T* begin(T(&arr)[N])
{
return arr;
}
template<typename T, std::size_t N>
constexpr T* end(T(&arr)[N])
{
return arr + N;
}
int main()
{
list<int> s = { 1,2,3 };
int x[3] = { 1,2,3 };
auto p1 = begin(s);
auto p2 = begin(x);
}
Ranged-for
- c#, java의 foreach
- C++11에서 도입
- STL 컨테이너, raw 배열에 있는 모든 요소에 접근하기 위한 편리한 방법 제공
- 컴파일러가 ranged-for 표현식을 for문 + begin, end를 통해 얻어진 반복자를 통해 요소에 접근하는 구문으로 변경
- begin과 end를 제공하는 모든 객체에 사용 가능
#include <iostream>
#include <list>
using namespace std;
struct Point3D
{
int x = 1;
int y = 2;
int z = 3;
};
int* begin(Point3D& p3) { return &(p3.x); }
int* end(Point3D& p3) { return &(p3.z)+1; }
int main()
{
Point3D p3;
for (auto& n : p3) // begin(p3)
cout << n << endl;
}
Delete / Default Function
- delete function : 암시적 형변환으로 인한 함수 호출시 오류 처리 할 때 처리 방법
template<typename T> void goo(T a)
{
}
void goo(double) = delete;
class Mutex
{
public:
Mutex(const Mutex&) = delete;
void operator=(const Mutex&) = delete;
//private:
// Mutex(const Mutex&);
};
int main()
{
goo(3.4); // Error
Mutex m1;
Mutex m2 = m1; // mutex 복사 생성시 error
}
- default function
#include <iostream>
#include <type_traits>
using namespace std;
struct Point
{
int x, y;
Point() {} // 사용자가 생성자 제공. trivial 하지 않음
//Point() = default; // 컴파일러가 제공. trivial 함
Point(const Point& ) = default; // 복사 생성자 default 컴파일로 구현 요청
Point(int a, int b) : x(a), y(b) {}
};
int main()
{
Point p1{};
cout << p1.x << endl; // default 생성자는 멤버 변수를 초기화 해줌 - 0
// 사용자 초기화에서 아무 일도 하지 않으면 멤버들은 garbage
cout << is_trivially_constructible<Point>::value
<< endl;
}
noexcept
예외가 없는 function임을 컴파일러에 알림
- 예외가 없음으로 알리면 보다 최적화된 코드가 생성됨
- 예외가 있는지 없는지 코드레벨에서 조사 할 수 있음
#include <iostream>
using namespace std;
/*
// c++98
int foo() // 예외가 있을수도 있고, 없을수도 있다.
int foo() throw(int) // 예외가 있다는 의미.
int foo() throw() // 예외가 없다는 의미.
{
throw 1;
return 0;
}
*/
// C++11
//void goo() noexcept(true) // 예외가 없다.
void goo() noexcept // 위와 동일.
{
throw 1;
}
void goo()
{
throw 1;
}
int main()
{
goo();
try
{
goo();
}
catch (...)
{
cout << "exception..." << endl;
}
}
- noexcept 여부 조사 방법
#include <iostream>
using namespace std;
void algo1()
{
// 빠르지만 예외 가능성이 있다.
}
void algo2() noexcept // 지정자
{
// 느리지만 예외가 나오지 않는다.
}
class Test
{
public:
Test() noexcept {}
};
int main()
{
bool b1 = noexcept(algo1()); // 0. 연산자
bool b2 = noexcept(algo2()); // 1
cout << b1 << ", " << b2 << endl;
bool b3 = is_nothrow_constructible<Test>::value; // 생성자의 noexcept 여부
cout << b3 << endl;
}
Scoped enum
enum 상수의 단점을 개선한 새로운 enum 상수 문법
- 기존 enum 상수의 단점
- 타입의 이름 없이 사용 가능(none namespace)
- 요소의 타입을 지정 할 수 없음
// C++98/03
//enum Color { red = 1, green = 2 };
// C++11
enum class Color : char { red = 1, green = 2 };
int main()
{
// int n1 = Color::red; // error
Color n1 = Color::red; // ok
int n2 = static_cast<int>(Color::red); // ok
int n3 = red; // error
// int red = 0;
// int n3 = red;
cout << typeid(underlying_type_t<Color>).name() << endl; // enum 요소 타입
}
User define literal
코드에서 literal 사용시 int a = 10k (10000) 이런식으로 명시 하는 방법
#include <iostream>
using namespace std;
int operator""_k(unsigned long long v)
{
return 1000 * v;
}
int main()
{
int n1 = 10; // meter
int n2 = 10_k;// 10000 operator""k(10)
cout << n2 << endl; /// 10000
}
- 사용 예제
#include <iostream>
using namespace std;
class second
{
int value;
public:
second(long long s) : value(s) {}
};
class minute
{
int value;
public:
minute(long long s) : value(s) {}
};
second operator""_s(unsigned long long v)
{
return v;
}
minute operator""_m(unsigned long long v)
{
return v;
}
int main()
{
second n1 = 10_s;
minute n2 = 10_m;
}
- 표준 STL iterals 사용 예제
#include <iostream>
#include <string>
#include <chrono>
using namespace std;
using namespace std::chrono;
using namespace std::literals;
void foo(string s) { cout << "string" << endl; }
void foo(const char* s) { cout << "char*" << endl; }
int main()
{
foo("hello"); // char*
foo("hello"s); // string operator""s("hello")
seconds s1 = 10s;
minutes m1 = 10min;
seconds s2 = 10min;
cout << s2.count() << endl; // 600
}
Delegate Constructor
위임 생성자 : 생성자 안에서 다른 생성자를 호출
#include <iostream>
using namespace std;
struct Point
{
int x, y;
//Point() : x(0), y(0) {}
Point() : Point(0,0) // 위임 생성자
{
// 다른 생성자를 호출할수 없을까 ?
//Point(0, 0); // 생성자 호출이 아닌, 임시객체를 생성하는 표현.
//new(this) Point(0, 0); // 예전 방법으로 다른 생성자를 호출하는 방법
}
Point(int a, int b) : x(a), y(b) {}
};
int main()
{
Point p;
cout << p.x << endl;
cout << p.y << endl;
}
Inherit Constructor
- 살펴보기 : 상속 관계에서 자식 클래스의 함수 이름과 부모 클래스의 이름이 같을 경우 사용하려면 using 키워드가 필요함
class Base
{
public:
void foo(int a) {}
};
class Derived : public Base
{
public:
using Base::foo;
void foo() { }
};
int main()
{
Derived d;
d.foo(10); // error, using 처리 필요
d.foo();
}
- 부모의 생성자를 이용 하고 싶을 경우 생성자를 상속받아 사용 가능(C++11 이후)
#include <iostream>
#include <string>
using namespace std;
class Base
{
string name;
public:
Base(string s) : name(s) {}
};
class Derived : public Base
{
public:
using Base::Base; // 생성자 상속
//Derived(string s) : Base(s) {} // 이전 방식
};
int main()
{
Derived d("aa");
}
Override
- override 키워드 : 자식 클래스에서 함수 재정의시 실수를 방지하는 방법
class Base
{
public:
virtual void f1(int) {}
virtual void f2() const {}
virtual void f3() {}
void f4() {}
};
class Derived : public Base
{
public:
virtual void f1(int) override {}
virtual void f2() const override {}
virtual void f3() override {}
//void f4() override {}
};
int main()
{
}
- final 키워드 : 이후 자식 클래스의 함수 재정의 또는 신규 상속을 막음
#include <type_traits>
#include <iostream>
using namespace std;
class A
{
public:
virtual void f1() {}
};
class B final : public A
{
public:
virtual void f1() override final{}
};
/*
class C : public B
{
public:
//virtual void f1() override {}
};
*/
int main()
{
bool b = is_final<B>::value;
cout << b << endl;
}