프로그래밍 일반 2014. 4. 14. 16:39

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");  // 오류

}


프로그래밍 일반 2014. 4. 10. 17:26

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    


프로그래밍 일반 2014. 4. 10. 15:09

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;

};


참고 :

위의 링크를 타고가면 생각할 거리들이 많이 있다.


프로그래밍 일반 2014. 4. 10. 12:31

C++ 11 테스트용 웹 컴파일러


C++ 11 을 접하고 새로운 기능들을 테스트해보 싶을 때 새로운 기능을 지원하는 컴파일러가 없다면


간단히 웹 컴파일러를 이용하여 공부해보자.


웹 컴파일러 링크 : http://melpon.org/wandbox/

프로그래밍 일반 2014. 4. 10. 11:52

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;

};


참고 :


프로그래밍 일반 2014. 4. 10. 11:13

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; // 오류 : 줄어듬


프로그래밍 일반 2014. 4. 9. 18:14

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";

}


참고 :


프로그래밍 일반 2014. 4. 8. 19:15

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]과 클래스의 특징[각주:2]을 합친 형태이기 때문이다. enum의 타입 자체를 명확하게 정의하면 열거형의 크기를 보장하고, 다른 것들과 쉽게 같이 사용할 수 있다.


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 }; )


참고






  1. 값을 가진 이름들 [본문으로]
  2. 범위를 가진 멤버들과 암시적 변환의 부재 [본문으로]
프로그래밍 일반 2014. 4. 2. 15:10

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


참고





  1. 예 istream [본문으로]
프로그래밍 일반 2014. 4. 2. 14:31

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[각주:1] 라는 것을 삭제함으로써 사용할 수 있게 되었다. 이제 두 언어 모두 모든 변수와 함수가 반드시 명확한 타입으로 정의 되어야만 한다. auto의 옛날 의미[각주:2]는 이제 사용할 수 없다.



참고






  1. 형식이 지정되지 않으면 int로 간주한다. [본문으로]
  2. 이 변수는 지역 변수이다 void f() { auto int i = 0; // C++0x에서는 오류 //... } [본문으로]