검색결과 리스트
C++ 11에 해당되는 글 12건
- 2014.04.14 C++ 11 컴파일 타임 assertion ( static_assert )
- 2014.04.10 C++ 11 생성자 상속 ( Inheriting Constructors )
- 2014.04.10 C++ 11 Non-static data member initializers
- 2014.04.10 C++ 11 테스트용 웹 컴파일러
- 2014.04.10 C++ 11 생성자 위임 Delegating constructors
- 2014.04.10 C++ 11 줄어듬 narrowing 방지
- 2014.04.09 C++ 11 Initializer lists 초기화자 리스트
- 2014.04.08 C++ 11 enum class
- 2014.04.02 C++ 11 Range-based for loop
- 2014.04.02 C++ 11 auto initializer
글
C++ 11 컴파일 타임 assertion ( static_assert )
웹 컴파일러 링크 : http://melpon.org/wandbox/
컴파일 타임 assertion 은 상수식과 문자열 리터럴로 구성되어 있다.
static_assert(constant-expression, string literal);
컴파일러는 식을 계산한 다음 expression 이 거짓이라면 오류 메시지에 string 을 적는다.
static_assert 는 컴파일시에 처리되기 때문에 런타임에 의존하는 값들을 확인할 때는 사용할 수 없다.
int Func(int* p, int n)
{
static_assert( NULL != p, "p is not null"); // 오류
}
설정
트랙백
댓글
글
C++ 11 생성자 상속 ( Inheriting Constructors )
웹 컴파일러 링크 : http://melpon.org/wandbox/
class CBase
{
public :
void Func(float f) {}
};
class CDerived : public CBase
{
public :
void Func(int i) {}
};
CBase bObj;
bObj.Func(3.1f); // Func(float f)
CDerived dObj;
dObj.Func(3.1f); // Func(int i) 호출 narrowing 발생
C++ 98 에서 아래 코드는 베이스 클래스로 부터 파생 클래스로 오버로딩 된 함수들을 옮길 수 있다.
class CBase
{
public :
void Func(float f) {}
};
class CDerived : public CBase
{
public :
using CBase::Func;
void Func(int i) {}
};
CBase bObj;
bObj.Func(3.1f); // Func(float f)
CDerived dObj;
dObj.Func(3.1f); // CBase::Func(float f) 호출
C++ 11 에서는 멤버 함수 뿐만 아니라 생성자들에도 이러한 기능을 사용할 수 있다.
class CDerived : public CBase
{
public :
using CBase::CBase; // 베이스의 생성자를 파생 클래스 범위로 가져온다. C++ 11 에서만 가능
CDerived(int i) {} // 이 생성자를 CBase::CBase(int i) 보다 선호한다.
using CBase::Func; // 베이스의 Func를 파생 클래스의 범위로 가져온다. C++ 98 작동
void Func(int i) {}
void Func(char c) {}
void Func(float f) {} // 이 Func 를 CBase::Func(float) 보다 선호한다.
private :
int m_iValue;
};
class CBase
{
public :
CBase(int iValue = 0) {}
void Func(float f) {}
};
class CDerived : public CBase
{
public :
using CBase::CBase; // 암시적으로 CDerived(int = 0) 선언
using CBase::Func;
void Func(int i) {}
void Func(char c) {}
private :
int m_iValue;
};
int main()
{
CBase bObj(10);
bObj.Func(3.1f); // Func(float f)
CDerived dObj(10); // 생성된다.
// dObj.m_iValue 가 초기화되지 않는다.
// 파생 클래스에서 베이스 클래스의 생성자를 상속할 때, 파생 클래스에서 새로운 멤버 변수를
// 초기화해야 한다면 이런 실수가 가능하다.
return 1;
}
위와 같은 문제는 멤버 초기화자 ( member initializer ) 를 이용하여 해결한다.
class CDerived : public CBase
{
public :
using CBase::CBase;
// ....
private :
int m_iValue{0};
};
CDerived dObj(10); // 생성되고 dObj.m_iValue 는 0
설정
트랙백
댓글
글
C++ 11 Non-static data member initializers
웹 컴파일러 링크 : http://melpon.org/wandbox/
C++ 98 에서는 오직 정수 타입의 정적 상수 멤버들 만이 클래스 내부에서 초기화 될 수 있었고 초기화 값은 반드시 상수 식이어야했다.
이러한 제약 조건은 컴파일 타임에 초기화가 될 수 있게 보장을 했다. 예를 들어
int var = 7;
class CTest {
static const int m1 = 7; // 가능
const int m2 = 7; // 오류 : static 아님
static int m3 = 7; // 오류 : const 아님
static const int m4 = var; // 오류 : 초기화값이 상수 식이 아니다
// const int var = 7 는 가능
static const string m5 = "odd"; // 오류 : 정수 타입이 아닙니다
// ...
};
C++ 11 에서는 기본적으로 비 정적 데이터 멤버들도 클래스 안에 선언된 부분에서 바로 초기화가 가능하다.
class CTest
{
private :
int m_iMember = 0;
};
는 아래 코드와 동일하다.
class CTest
{
public :
CTest() : m_iMember(0) {}
private :
int m_iMember = 0;
};
이는 타이핑을 줄여줄뿐만 아니라 여러개의 생성자를 가지고 있는 클래스들을 사용할 때 도움이 된다.
많은 경우 모든 생성자가 특정 멤버에 대해 공통적인 초기화 값을 가지는 경우가 있다.
class C {
public:
A(): a(7), b(5), hash_algorithm("MD5"), s("Constructor run") {}
A(int a val) : a(a val), b(5), hash_algorithm("MD5"), s("Constructor run") {}
A(D d) : a(7), b(Func(d)), hash_algorithm("MD5"), s("Constructor run") {}
private:
HashingFunction hash_algorithm; // 모든 A 인스턴스들에 대해 암호화 된 해시가 적용된다
std::string s; // 객체 생존사이클에 대한 상태를 나타내기 위한 문자열
int a, b;
};
hash_algorithm 과 s 모두 디폴드 값이 있다는 사실을 위의 혼잡한 코드에서 쉽게 눈치채기는 어렵고 유지 보수시에 문제가 될 가능성이 있다.
대신 C++ 11 에서는 데이터 멤버들의 초기화를 다음과 같이 수행할 수 있다.
class A {
public:
A(): a(7), b(5) {}
A(int a val) : a(a val), b(5) {}
A(D d) : a(7), b(Func(d)) {}
private:
HashingFunction hash_algorithm{"MD5"}; // 모든 A 인스턴스들에 대해 암호화 된 해시가 적용된다
std::string s{"Constructor run"}; // 객체 생존사이클에 대한 상태를 나타내기 위한 문자열
int a, b;
};
만약 멤버가 클래스 내부와 생성자 양쪽에서 초기화 된다면 생성자에 의한 초기화만 수행된다. (즉, 디폴트를 오버라이드한다.)
따라서 아래와 같이 더 간단한 표현이 가능하다.
class A {
public :
A() {}
A(int a_val) : a(a_val) {}
A(D d) : b(Func(d)) {}
private:
HashingFunction hash_algorithm{"MD5"}; // 모든 A 인스턴스들에 대해 암호화 된 해시가 적용된다
std::string s{"Constructor run"}; // 객체 생존사이클에 대한 상태를 나타내기 위한 문자열
int a = 7;
int b = 5;
};
참고 :
- [N2628 = 08 − 0138] Michael Spertus and Bill Seymour: Non-static data member initializers.
설정
트랙백
댓글
글
C++ 11 테스트용 웹 컴파일러
C++ 11 을 접하고 새로운 기능들을 테스트해보 싶을 때 새로운 기능을 지원하는 컴파일러가 없다면
간단히 웹 컴파일러를 이용하여 공부해보자.
웹 컴파일러 링크 : http://melpon.org/wandbox/
설정
트랙백
댓글
글
C++ 11 생성자 위임 Delegating constructors
웹 컴파일러 링크 : http://melpon.org/wandbox/
만일 여러 생성자가 같은 일을 하기 원한다면 초기화 함수를 만들어 생성자에서 호출하도록 할 것이다.
class CTest
{
public :
CTest()
{
Init(0);
}
CTest(int iValue)
{
Init(iValue);
}
CTest(std::string szString)
{
int iCastValue = boost::lexical_cast<int>(szString);
Init(iCastValue);
}
void Init(int iValue) {
if ( 0 < iValue && iValue < MAX ) {
m_iMember = iValue;
}
else throw error_value(iValue);
// ....
}
private :
int m_iMember;
};
하지만 위의 코드는 유지 보수가 불편하고 오류가 발생할 확률도 높을 뿐더러 작성해야하는 코드도 길다.
이런 불편을 해결하기 위해서 C++ 11 에서는 한 개의 생성자를 정의하여 다른 생성자에서 사용할 수 있게 하였다.
class CTest
{
public:
CTest(int iValue)
{
if ( 0 < iValue && iValue < MAX ) {
m_iMember = iValue;
}
else throw error_value(iValue);
// ....
}
CTest() : CTest{0} {}
CTest(std::string szString) : CTest{ boost::lexical_cast<int>(szString) } {}
private:
int m_iMember;
};
참고 :
- [N1986 == 06 − 0056] Herb Sutter and Francis Glassborow: Delegating Constructors (revision 3).
설정
트랙백
댓글
글
C++ 11 줄어듬 narrowing 방지
웹 컴파일러 링크 : http://melpon.org/wandbox/
C와 C++의 문제점으로 아래와 같은 것을 암시적으로 잘라낸다.
int x = 7.3; // 데이터 손실
void f(int);
f(7.3); // 데이터 손실
하지만 C++ 11 에서 {} 초기화는 이와 같은 narrowing cast 를 허용하지 않는다.
int x0 {7.3}; // 오류 : 줄어듬
int x1 = {7.3}; // 오류 : 줄어듬
double d = 7;
int x2{d}; // 오류 : 줄어듬 (double 에서 int 로)
char x3{7}; // 가능 : 7 이 비록 int 이지만 이는 줄어듬이 아니다
vector<int> vi = { 1, 2.3, 4, 5.6 }; // 오류 : double 에서 int 로 줄어듬
C++ 11 이 많은 수의 호환성 문제를 방지할 수 있는 이유는 바로 가능한 초기화자의 타입이 아닌 실제 값을 비교해서 줄으듬인지 아닌지 판단하기 때문이다. 만일 어떠한 값이 목표한 타입과 정확히 동일하게 같은 값으로 표현할 수 있다면 그 타입 변환은 줄어듬이 아니다.
char c1{7}; // 7 은 int 이지만 char 에 포함되므로 가능하다
char c2{77777}; // 오류 : 줄어듬
부동 소수점 - 정수 간 변환은 언제나 줄어듬으로 판단한다.
int ivalue = 7.0; // 오류 : 줄어듬
설정
트랙백
댓글
글
C++ 11 Initializer lists 초기화자 리스트
웹 컴파일러 링크 : http://melpon.org/wandbox/
std::vector<double> v = { 1, 2, 3.456, 99.99 };
std::list<std::pair<std::string,std::string>> languages = {
{”Nygaard”,”Simula”},
{”Richards”,”BCPL”},
{”Ritchie”,”C”}
};
std::map<std::vector<std::string>,std::vector<int>> years = {
{ {”Maurice”,”Vincent”, ”Wilkes”},{1913, 1945, 1951, 1967, 2000} },
{ {”Martin”, ”Ritchards”}, {1982, 2003, 2007} },
{ {”David”, ”John”, ”Wheeler”}, {1927, 1947, 1951, 2004} }
};
초기화자 리스트는 단순히 배열만을 위해 사용되는 것은 아니다. {} 리스트를 받는 메커니즘은 std::initializer_list<T> 를 인자로 받는 함수(많은 경우 생성자)와 같다. 예를 들어
void Func(std::initializer_list<int>);
Func({1,2});
Func( { 23, 2344, 23434, 23523} );
Func( {} ); // 비어 있는 리스트
Func{1, 2}; // 오류 : 함수 호출 ()가 없다.
years.insert( {{”Martin”, ”Ritchards”}, {1982, 2003, 2007}} );
초기화자 리스트는 임의의 길이가 될 수 있지만 모두 같은 타입이어야 한다. ( 즉 모든 원소들이 템플릿 인자 타입 T 이거나, T 로 변환될 수 있어야 한다. )
template<class T> class vector {
public:
vector (std::initializer_list<T> s) // 초기화자 리스트 생성자
{
reserve(s.size()); // 올바른 크기의 공간을 얻는다
// 원소들을 초기화 한다. (elem[0:s.size()) 안에 있는 것들을)
uninitialized_copy(s.begin(), s.end(), elem);
sz = s.size(); // vector 크기를 설정한다
}
// ... as before ...
};
직접적으로 초기화하는 것과 복사 초기화하는 것의 차이는 {}를 이용한 초기화에서도 마찬가지로 적용된다.
예를 들어 std::vector 에는 int 로 부터 생성하는 명시적 생성자와 초기화자 리스트 생성자가 있다.
std::vector<int> v1(10); // 가능 : v1에는 10개의 원소가 있다.
v1 = 10; // 오류 : 허용되는 변환이 없다.
std::vector<int> v2 = 10; // 오류 : int 를 std::vector<int> 로 변환할 수 없다.
void Func( const std::vector<int>& );
Func( 9 ); // 오류 : int 를 std::vector<int> 로 변환할 수 없다.
std::vector<int> v1{10}; // 가능 : v1에는 1개의 원소가 있다 (값이 10)
v1 = {9}; // v1 에는 1개의 원소가 있다. (값이 9)
Func( {9} ); // Func는 리스트 {9} 로 호출
std::vector<std::vector<int>> vs = {
std::vector<int>(10), // 가능 : 명시적 생성 (10 개의 원소)
std::vector<int>{10}, // 가능 : 명시적 생성 (값이 10 인 1 개의 원소)
10 // 오류 : vector 의 생성자는 명시적
};
함수는 initializer_list 를 인자로 접근할 수 있다. 예를 들어
void f(initializer_list<int> args)
{
for (auto p=args.begin(); p!=args.end(); ++p) cout << *p << "\n";
}
참고 :
- [N1890 = 05 − 0150] Bjarne Stroustrup and Gabriel Dos Reis: Initialization and initializers (an overview of initialization-related problems with suggested solutions).
- [N1919 = 05 − 0179] Bjarne Stroustrup and Gabriel Dos Reis: Initializer lists.
- [N2215 = 07 − 0075] Bjarne Stroustrup and Gabriel Dos Reis :Initializer lists (Rev. 3) .
- [N2640 = 08 − 0150] Jason Merrill and Daveed Vandevoorde: Initializer Lists – Alternative Mechanism and Rationale (v. 2) (final proposal).
설정
트랙백
댓글
글
C++ 11 enum class
웹 컴파일러 링크 : http://melpon.org/wandbox/
기존 enum은 암시적으로 int형으로 형변환 되기 때문에 enum이 정수로써 작동되기를 원하지 않을 때 오류를 발생 시킬 수 있다.
허용되는 범위 밖에서도 열거 이름들을 사용할 수 있었기 때문에 이름간의 충돌이 발생한다.
그러나 새로운 enum class는 타입과 정의 범위가 명확하다.
enum Alert { green, yellow, election, red }; // 이전의 enum
enum class Color { red, blue }; // 범위가 정해져있고, 타입이 잘 정해진 enum
// 이를 포함하고 있는 범위 바깥에서는 열거형을 사용 불가
// int 로 암시적으로 변환되지 않는다.
enum class TrafficLight { red, yellow, green };
Alert a = 7; // 오류 (모든 C++ 버전에서)
Color c = 7; // 오류 (int 에서 Color 로 변환 불가)
int a2 = red; // 가능 (Alert 에서 int 로 변환 가능)
int a3 = Alert::red; // C++ 98 에서는 오류. C++ 11 에서는 가능
// vs 2008 ver. 9.0.30729.1 sp 에서 warring
int a4 = blue; // 오류 : blue 가 범위에 없다
int a5 = Color::blue; // 오류 : Color 에서 int 로 변환 불가능
Color a6 = Color::blue; // 가능
새롭게 도입한 열거형의 이름이 "enum class"인 이유는 전통적인 enum의 특징들과 클래스의 특징 1을 합친 형태이기 때문이다. enum의 타입 자체를 명확하게 정의하면 열거형의 크기를 보장하고, 다른 것들과 쉽게 같이 사용할 수 있다. 2
enum class Color : char { red, blue }; // 함축된 표현
enum class TrafficLight { red, yellow, green }; // 디폴트로 내장 타입은 int 로 처리된다.
enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFF0U }; // E 의 크기가 얼마나 클까요?
// 이전의 규칙에 따르면 -
// 구현에 따라 다르다(implementaion defined)
enum EE : unsigned long { EE1 = 1, EE2 = 2, EEbig = 0xFFFFFFF0U }; // 이제 구체적으로 정할 수
있습니다
enum class Color code : char; // 전방 선언, VS 2008 에서 사용 가능
void foobar(Color code* p); // 전진 선언을 사용
// ...
enum class Color code : char { red, yellow, green, blue }; // 정의
// VS 2008 에서 사용 가능
msdn C++ 레퍼런스를 확인하면
enum [tag] [: type] {enum-list} [declarator]; // for definition of enumerated type enum tag declarator; // for declaration of variable of type tag
// 아래의 코드를 한번씩 확인해보자..
enum Alert { green, yellow, election, red = 400000000 }; //enum Alert { green = -1, yellow, election, red = 4000000000 }; //enum Alert : INT64 { green = -1, yellow, election, red = 40000000000 }; // VS 2008에서 사용 가능
enum 의 내장 타입은 반드시 부호 있는/없는 정수 타입이어야만 합니다. 디폴트는 int 입니다. 표준 라이브러리에서 enum class 는 다음과 같은 경우로 사용됩니다.
- 시스템의 특별한 오류 코드들을 매핑하기 위해서 (enum class errc)
- 포인터 안정성 표시자(indicator) (enum class pointer safety {relaxed, preferred, strict};)
- I/O 스트림 오류들 (enum class io errc {stream = 1 }; )
- 비동기화통신에서의오류처리를위해(enum class future errc {broken promise, future already retrieved,promise already satisfied }; )
참고
- [N1513 = 03 − 0096] David E. Miller: Improving Enumeration Types (original enum proposal)
- [N2347 = J16/07 − 0207] David E. Miller, Herb Sutter, and Bjarne Stroustrup: Strongly Typed Enums (revision 3)
- [N2499 = 08 − 0009] Alberto Ganesh Barbati: Forward declaration of enumerations
설정
트랙백
댓글
글
C++ 11 Range-based for loop
웹 컴파일러 링크 : http://melpon.org/wandbox/
Range for
range 안에서 STL의 begin(), end() 에서 하는 것처럼 루프를 실행한다.
모든 표준 컨테이너들은 range로 사용될 수 있고 std::string, initializer 리스트, 배열, 그리고 begin()과 end()를 정의할 수 있는 모든 것들이 사용 가능하다. 1
문법
attr(optional) for ( range_declaration : range_expression ) loop_statement
위의 구문은 다음과 유사한 코드를 생성한다.
{
auto && __range = range_expression ;
for (auto __begin = begin_expr,
__end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
샘플 코드
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v = {0, 1, 2, 3, 4, 5};
for (int &i : v) // access by reference (const allowed)
std::cout << i << ' ';
std::cout << '\n';
for (auto i : v) // compiler uses type inference to determine the right type
std::cout << i << ' ';
std::cout << '\n';
for (int i : v) // access by value as well
std::cout << i << ' ';
std::cout << '\n';
}
output
0 1 2 3 4 5
0 1 2 3 4 5
0 1 2 3 4 5
참고
- [N2243 = 07 − 0103] Thorsten Ottosen: Wording for range-based for-loop (revision 2)
- [N3257 = 11 − 0027] Jonathan Wakely and Bjarne Stroustrup: Range-based for statements and ADL (Option 5 was chosen).
- 예 istream [본문으로]
설정
트랙백
댓글
글
C++ 11 auto initializer
웹 컴파일러 링크 : http://melpon.org/wandbox/
auto initializer 로부터 타입을 유추
예를 들어
auto x = 7;
위 코드에서 x는 initializer의 타입이 int 이기 대문에 int 타입이 된다.
일반적으로 우리는
auto x = expression;
과 같은 구문을 보면 x는 expression 타입이 될 것이라고 생각한다.
auto 를 사용하는 주된 이유는 어떠한 식의 타입이 알기 어렵거나 쓰기 매우 길 경우이다. 예를 들어
template<class T> void printall(const vector<T>& v) {
for (auto p = v.begin(); p != v.end(); ++p) {
cout << *p << "\n";
}
}
와 같은 코드를 이전 C++ 코드에서는
template<class T> void printall(const vector<T>& v) {
for (typename vector<T>::const_iterator p = v.begin(); p != v.end(); ++p) {
cout << *p << "\n";
}
}
으로 작성해야만 했다. 또한 변수의 타입이 템플릿 인자에 의해 좌우 될 때, 이를 auto를 사용하지 않고 코드를 작성하려면 매우 까다롭다.
template<class T, class U> void multiply(const vector<T>& vt, const vector<U>& vu) {
// ...
auto temp = vt[i] * vu[i];
// ...
}
temp 의 타입은 T와 U를 곱한 것의 타입이 되어야만 하며, 이는 사람이 얽었을 때 무엇인지 생각하기에 까다롭지만 컴파일러는 어떠한 T와 U에 대한 처리인지 알기 대문에 위와 같이 표현할 수 있다.
auto는 1984년에 이미 구현되었지만 당시의 C와 호환성 문제 때문에 사용할 수 없었다가 C++98 과 C99 에서 암시적 int 라는 것을 삭제함으로써 사용할 수 있게 되었다. 이제 두 언어 모두 모든 변수와 함수가 반드시 명확한 타입으로 정의 되어야만 한다. auto의 옛날 의미 1는 이제 사용할 수 없다. 2
참고
- N1984 = 06 − 0054] Jaakko Jarvi, Bjarne Stroustrup, and Gabriel Dos Reis: Deducing the type of variable from its initializer expression (revision 4).
RECENT COMMENT