태그 : Cpp

[C++]3. 낌새만 보이면 const 를 들이대 보자!(4)

상수멤버 비상수 멤버함수에서 코드중복 현상을 피하는 방법

mutable생각지도 않던 비트 수준 상수성단어 하나로 깔끔하게 해결해 준다.
경계검사라든지 접근정보로깅, 자료무결성검증과 같은 코드들이 들어간다면 상수/비상수 버전의 코드에는
같은 코드들이 Ctrl+C , Ctrl+V 되게 것이다.

class TextBlock{
public:
            
             const char& operator[] ( std::size_t position ) const
             {
                           ...                        //
경계검사  -- Ctrl+C
                           ...                        //
접근데이터로깅-- Ctrl+C
                           ...                        //
자료무결성검증-- Ctrl+C
                           return text[position];
             }

             char& operator[] ( std::size_t position )
             {
                           ...                        //
경계검사  -- Ctrl+V
                           ...                        //
접근데이터로깅-- Ctrl+V
                           ...                        //
자료무결성검증-- Ctrl+V
                           return text[position];
             }
private:
             std::string text;
};

operator핵심 기능은 한 번만 구현하고 사용은 못할까?
const
캐스팅으로 날려보자!

기본적으론 좋지 않은 아이디어이지만
비상수 operator[]호출한다면 거기엔 비상수 객체가 우선적으로 들어있을 것이기 때문에 안정성이 있다.
따라서,  비상수 operator[]상수 버전을 호출하도록 구현을 하자

class TextBlock{
public:
            
             const char& operator[] ( std::size_t position ) const
             {
                           ...                        //
이전과 동일
                           return text[position];
             }

char& operator[] ( std::size_t position ) //상수 버전 op호출한다
{
       return
       const_cast<char&> (     //op[]
반환형에 캐스팅을 하여 const때고
               static_cast<const TextBlock&> // *this타입에 const붙여
                      (*this)[position]  );             //op[]
상수 버전을 호출
}

private:
             std::string text;
};

그냥 operator[]호출하면 무한 루프가 돌기 때문에 상수 operator[]호출 하기위해 *this
const TextBlock& 으로 캐스팅을 하였고 다음에 operator[]반환 값에서 const떼어 내야 하기 때문에 번의 캐스팅이 시행되었다.

const붙이는 캐스팅은 안전한 타입 변환(비상수 객체에서 상수 객체로 바꾸는)강제로 진행하는 것뿐이라
static_cast사용 하고 const제거 하는 캐스팅은 const_cast 밖에 없기 때문에 사용을 하였다.

하지만 상수 멤버에서 비상수 멤버를 호출 하게 해서는 절대 안된다. 상수 멤버함수는 해당객체의 논리적인
상태를 바꾸지 않겠다고 컴파일러와 협의가 되어 있지만 비상수 멤버함수는 그렇지 않기 때문이다.

by 일찍자라 | 2007/04/05 21:29 | Effective C++ | 트랙백

[C++]3. 낌새만 보이면 const 를 들이대 보자!(3)

비트수준 상수성(bitwise constness)과 논리적 상수성(logical constness)


비트수준 상수성 ( 물리적 상수성, physical constness )
멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야(정적 멤버 제외) 그 멤버 함수가 'const' 이다
즉, 멤버 함수가 객체의 구성요소들 중 아무것도 건드리지 말아야 한다는 것이지만
class CTextBlock {
public:

    char& operator[] ( std::size_t position ) const //부적절 하지만 비트 수준 상수성에 있어
    {  return pText[position]; }            //허용되는 선언
private:
    char* pText;    // string 으로 선언할 수도 있으나 혹시나 C 와의 호환성을 위해 선언했다면
};

위와 같은 경우에서는 다음과 같은 경우가 발생한다
const CTextBlock cctb( "Hello" );    //상수 객체 선언

char *pc = &cctb[0];        //상수 버전의 operator[]를 호출 cctb내부데이터의 포인터획득

*pc = 'J';            // cctb 는 "Jello"라는 값을 가지게 된다.
의도하지 않은 결과가 나올 수도 있기 때문에 이를 보완하기 위해 생겨난 개념이 논리적 상수성이다

논리적 상수성
일부 몇 비트는 바꿀 수 있되, 그것을 사용자 측에서만 알아채지 못하게만 하면 상수 멤버의 자격이 있다.
class CTextBlock {

public:

    std::size_t length() const;

private:
    char *pText;
    std::size_t textLength;    //바로 직전에 계산한 텍스트 길이
    bool lengthIsValid;        //이 길이가 현재 유효한가?
};

std::size_t CTextBlock::length() const
{
    if ( !lengthIsValid ) {
        textLength = std::strlen( pText ); //Error! 상수멤버안에서는

        lengthIsValid = true;      // textlength , lengthIsValid에 대입할 수 없다
    }
    return textLength;
}

CTextBlock의 상수 객체에 대해선 문제가 없어야 할 것 같은 코드이지만
length 의 구현은 비트수준 상수성과 너무 거리가 멀어 컴파일러의 검열을 통과할 수가 없습니다.


mutable

mutable 은 비정적 데이터 멤버를 비트수준의 상수성의 족쇄에서 풀어주는 약수 같은 키워드이다
class CTextBlock {

public:

    std::size_t length() const;

private:
    char *pText;
    mutable std::size_t textLength; //이제 이 데이터 들은 어떤 순간에도(상수멤버
    mutable bool lengthIsValid;     // 함수안이라도) 수정이 가능합니다.
};

std::size_t CTextBlock::length() const
{
    if ( !lengthIsValid ) {
        textLength = std::strlen( pText ); //OK!
        lengthIsValid = true;           // OK!
    }
    return textLength;
}

by 일찍자라 | 2007/04/05 21:10 | Effective C++ | 트랙백

[C++]3. 낌새만 보이면 const 를 들이대 보자!(2)

상수 멤버 함수

멤버 함수에 붙는 const 키워드는 다음과 같은 사실을 알려 준다.
"해당 멤버 함수가 상수 객체에 대해 호출될 함수이다"


 상수 멤버 함수의 중요성

1. 클래스의 인터페이스를 이해하기 좋게 한다
사용자는 클래스로 만들어진 객체를 변경할 수 있는 함수를 알고 있어야 한다.

2. 상수 객체를 사용할 수 있게 하자
객체 전달을 '상수 객체에 대한 참조자( reference-to-const )' 로 진행을 하게 되면
C++ 프로그램의 실행 성능을 높일 수 있다.


상수 멤버 함수 오버로딩

const 가 있고 없고의 차이만 있는 멤버 함수들은 오버로딩이 가능하다.

class TextBlock {
public:

    const char& operator[] ( std::size_t position ) const //상수 객체에 대한 operator[]
    { return text[position] ; }

    char& operator[] ( std::size_t position ) //비상수 객체에 대한 operator[]
    { return text[position]; }
private:
    std::string text;
};

위처럼 선언된 TextBlock의 operator는 다음과 같이 사용한다

TextBlock tb ("Hello");
std::cout << tb[0];        //TextBlock::operator[] 의 비상수 멤버를 호출

const TextBlock ctb("World");
std::cout << ctb[0];        //TextBlock::operator[] 의 상수 멤버를 호출

실제 프로그램에서 상수 객체가 생기는 경우는
상수 객체에 대한 포인터 혹은 상수객체의 참조자로 객체가 전달 될 때이다.

operator[] 를 오버로드(overload) 해서 각 버전마다 반환 타입이 다르기 때문에
TextBlock의 상수 객체와 비상수 객체의 쓰임새가 달라진다.
std::cout << tb[0];    // OK. 비상수 버전의 TextBlock객체를 읽음

tb[0] = 'x' ;        // OK. 비상수 버전의 TextBlock 객체 사용

std::cout << ctb[0];    // OK. 상수 버전의 TextBlock 객체를 읽음

ctb[0] = 'x' ;         // Error! 상수 버전의 TextBlock 객체에 쓰기는 안됨
여기서 발생하는 Error는 operator[] 의 반환타입 (return type)때문에 생기게 되는 것이고
operator[] 의 호출이 잘못된 것이 아니라는 것을 알아둡시다.
// const char& 타입에 대입연산을 시도하였기 때문

by 일찍자라 | 2007/04/05 21:07 | Effective C++ | 트랙백

[C++]3. 낌새만 보이면 const 를 들이대 보자!(1)

const 를 한마디로

의미적인 제약을 소스 코드 수준에서 붙인다는 점 //const 키워드가 붙은 객체는 외부 변경을 불가능하게 한다컴파일러가 이 제약을 단단히 지켜준다


const 의 기본 사용 방식

char greeting[] = “Hello”;

char *p = greeting; //비상수 포인터, 비상수 데이터

const char *p = greeting; //비상수 포인터, 상수 데이터

char * const p = greeting; //상수 포인터, 비상수 데이터

const char * const p = greeting; //상수 포인터, 상수 데이터

const 키워드가 * 표의 왼쪽에 있으면 포인터가 가리키는 대상이 상수
const 키워드가 * 표의 오른쪽에 있으면 포인터 자체가 상수

참고로 포인터가 가리키는 대상을 상수로 만들 시
void f1 ( const Widget *pw );

void f2 ( Widget const *pw );

둘 다 같은 의미이다. 위의 설명과 같이 * 의 왼쪽이냐 오른쪽이냐가 중요하다.


STL 반복자( iterator ) 에서의 const

iterator 는 포인터를 본뜬 것이고 기본적인 동작원리는 T* 포인터와 같다.
즉, iterator 를 const 로 선언하는 것은 포인터를 상수로 선언하는 것(T* const 포인터)과 같다.
iterator 는 자신이 가리키는 대상이 아닌 것을 가리키면 안되지만 가리키는 대상 자체는 변경이 가능하다.
만약 변경이 불가능한 객체를 가리키는 iterator( const T* 포인터 )가 필요하다면 const_iterator 를 쓰면 된다.
std::vector vec;

const std::vector::terator iter = vec.begin(); // iter 는 T* const 처럼 동작한다.
*iter = 10; // iter가 가리키는 대상을 변경한다
++iter; // Error! Iter 는 상수이다.

std::vector::const_iterator cIter = vec.begin(); // cIter 는 const T* 처럼 동작한다.
*cIter = 10; // Error! *cIter 가 상수이다
++cIter; // cIter를 변경하기 때문에 상관없다


함수 선언에서의 const

함수 선언문에 있어서 반환 값, 매개변수, 멤버 함수, 함수 전체 에 대해 const를 붙일 수 있다.
함수 반환값을 상수로 정해줄 시 사용자 측의 에러 돌발 상황을 줄이는 효과를 줄 수 있다
class Rational { … };

const Rational operator* ( const Rational& lhs, const Rational& rhs );

operator* 의 반환 값이 상수 객체가 되게 되면 다음과 같은 실수를 미연에 방지할 수 있다.
Rational a, b, c;

( a * b ) = c ; // a*b 의 결과에 operator= 를 호출하거나

if ( a * b = c ) .. // 실수로 생겨난 오타 == → = 를 방지할 수 있다

by 일찍자라 | 2007/04/04 01:36 | Effective C++ | 트랙백(1)

[C++]C++ 언어 표준 규격

ISO 에서 제정한 C++ 언어의 표준 규격입니다.

 

by 일찍자라 | 2007/04/04 01:26 | Knowledge-Warehouse | 트랙백

[C++]2. #define을 쓰려거든 const, enum, inline을 떠올리자(2)

나열자 둔갑술 ( enum hack )

클래스 상수의 초기값을 상수의 정의 시점에 둘 때 한가지 예외가 있다.
해당 클래스를 컴파일 하는 도중에 상수의 값이 필요한 때가 그때이다.

/* in GamePlayer.h */
class GamePLayer{
private:
    int scores[NumTurns]; //이와 같은 경우

};

컴파일러는 컴파일 과정에서 이 배열의 크기를 알아 내야 하는데 정수 타입의 정적
클래스 상수에 대한 클래스 초기화를 금지하는(표준에 어긋난 구식이지만) 구식 컴파일러의
대처 방법으로 나열자 둔갑술을 사용할 수 있다

/* in GamePlayer.h */
class GamePLayer{
private:
enum { NumTurns = 5 ; } //통칭 나열자 둔갑술
    int scores[NumTurns]; //해결!

};

나열자 둔갑술의 특징
1. 동작 방식이 const보다는 #define에 더 가깝다
// const 의 주소를 얻는 것은 합당하나 enum 의 주소를 얻는 것은 합당하지 않다.
2. 어떤 형태의 쓸데없는 메모리 할당을 하지 않는다.
3. 많은 코드에서 이 기법이 사용되고 있다


Macro 함수 보다는 inline 함수를 사용하자

#define CALL_WITH_MAX( a, b ) f( (a) > (b) ? (a) : (b) ) //A와B중 큰 것을 f에 넘겨 호출
Macro작성시 기본적으로 괄호를 씌어 주어야 하나 이것으로 끝나는 것이 아니다

int a = 5, b = 0;

CALL_WITH_MAX( ++a, b ); //a가 두 번 증가합니다
CALL_WITH_MAX( ++a, b+10 ); //a 가 한 번 증가합니다

위와 같은 사태를 막기 위해서 inline 함수에 대한 Template 만들어 준다.

template<typename T>    //T가 정확히 무엇인지 모르기 때문에
inline void callWithMax( const T& a, const T& b ) //매개변수로 상수 객체의 참조자를 쓴다
{
       f( a > b ? a : b );
}

이 함수는 템플릿이기 때문에 동일 계열 함수군(family of functions : 하나의 템플릿을 통해
만들어 질 것으로 예측 가능한 모든 함수들을 통칭)을 만들어 낸다.

by 일찍자라 | 2007/04/03 14:41 | Effective C++ | 트랙백

[C++]2. #define을 쓰려거든 const, enum, inline을 떠올리자(1)

가급적 전처리자 보다 컴파일러를 더 가까이하자.

아래코드를 보면

#define CIRCLE_RATIO 3.1415

우리에겐 CIRCLE_RATIO 가 기호식이름(symbolic name) 으로 보이지만 소스코드가 컴파일러에게 넘어가기
전에 전처리자가 숫자 상수로 바꾸어 버리기 때문에 컴파일러의 Symbol Table 에 들어가지 않는다.
그래서 에러 발생시 메시지엔 CIRCLE_RATIO가 아닌 3.1415가 있어 사용자를 헷갈리게 할 수 가 있다.


이 문제의 해결 방법으로는 Macro 대 상수를 쓰는 것이다.

const double CircleRatio = 3.1415 ;// 대문자로만 표기하는 이름은 대게 Macro 에서만 사용
언어차원에서 지원하는 상수 데이터 값이기 때문에 컴파일러의 눈에도 보이고 Symbol Table에도 들어간다
또한 이유는 Macro 를 쓰면 코드에 매칭되는 모든 곳에 3.1415로 바꾸게 되지만, 상수 타입은 아무리 여러 번
쓰더라도 사본은 딱 하나만 생기기 때문에 최종코드의 크기가 작아진다


#define 을 상수로 교체할 때의 유의 사항

1.상수 포인터 ( constant pointer ) 를 정의 하는 경우
상수 정의는 대게 Header 파일에 넣고 다른 소스 파일이 include 해서 쓰게 되므로 Pointer 는
꼭 const로 선언해주고 아울러 포인터가 가리키는 대상도 const 로 선언하는 것이 보통이다.

const char* const autorName = "Scott Meyers";

하지만 C++ 에서는 string 객체가 있으므로 다음의 선언이 더 권장된다.

const std::string authorName ( "Scott Meyers" );

2.클래스 멤버로 상수를 정의하는 경우
어떤 상수의 유효범위를 클래스로 한정하고 싶을 때 그 상수를 멤버로 만들어야 하는데
그 상수의 사본 개수가 한 개를 넘지 않게 하려면 정적( static ) 멤버로 만들어야 한다.

/* in GamPlayer.h */
class GamePlayer {
private:
    static const int NumTurns = 5; // 상수 선언
    int scores[NumTurns]; // 상수를 사용하는 부분
    …
};
위의 NumTurns는 선언(declaration) 된 것이다. 정의 (definition) 이 아님을 유의하자.
C++ 에서는 사용하고자 하는 것에 대해 정의(definition) 가 마련되어야 하는 것이 보통이지만
정적 멤버로 만들어 지는 정수류 ( 각종 정수 타입, char, bool 등 ) 타입의 클래스 상수는 예외이다.
이들에 대해 주소를 취하지 않는 한 , 정의 없이 선언만 해도 아무 문제가 없다 .
하지만 정의를 해달라고 닥달하는 구닥다리 컴파일러의 경우는
/* in GamPlayer.cpp */
const int GamePlayer::NumTurns;
// 정의에 값이 없는 이유는 클래스 상수의 초기값은 해당 상수가 선언된 시점에 바로 주어지기 때문
#define 은 일단 정의 되면 컴파일이 끝날 때 까지 유효하기 때문에 클래스 상수를 선언하는 데에는
쓸 수도 없고 어떤 형태의 캡슐화 혜택도 받을 수가 없다.

오래된 컴파일러는 위의 문법이 먹히지 않는 경우가 있는데 그때는

/* in CostEstimate.h */
class CostEstimate {
private:
    static const double FudgeFactor; // 정적 클래스 상수의 선언
    …
};
/* in CostEstimate.cpp */
const double CostEstimate::FudgeFactor = 1.35; // 정적 클래스 상수의 정의

by 일찍자라 | 2007/04/03 11:54 | Effective C++ | 트랙백(1)

Effiective C++



 Scott Meyers 의 명저


 프로젝트를 몇 개 해보면서 언어의 모든 것을

 알지 못하고 있다는 것을 크게 느끼게 해준 책

 틈틈히 읽고 또 읽고 있다.

 이번 기회에 정리를 한번 해보려 한다



by 일찍자라 | 2007/03/02 14:52 | Recently Reading | 트랙백

[C++]1. C++에 왔으면 C++의 법을 따르자.

C++을 여러 언어들의 연합체(federation)으로 보자

1. C
2. 객체지향 개념의 C++
3. 템플릿 C++
4. STL

C++는 하위언어들로 구성되어 있다!
C++의 어떤 부분을 사용하느냐에 따라 효과적인 프로그래밍의 규칙은 경우에 따라 달라진다.

by 일찍자라 | 2006/11/09 02:06 | Effective C++ | 트랙백

[C++]0. 용어설명

declaration(선언)
 -  코드에 사용되는 어떤 대상의 이름과 타입을 컴파일러에게 알려주는 것
 -  구체적인 세부사항은 들어 있지 않음
extern int x; //객체 선언

std::size_t numDigits(int number); // 함수 선언

class Widget; // 클래스 선언

template<typename T>
class GraphNode;        // 템플릿 선언

signature(시그너처)
 -  함수의 매개변수 리스트와 반환 타입
위의 numDigit 함수의 Signature는 std::size_t(int).

definition(정의)
 -  선언에서 빠진 구체적인 세부사항을 컴파일러에게 제공

initialization(초기화)

 -  어떤 객체에 최초의 값을 부여하는 과정
 -  사용자 정의 타입으로 생성한 객체의 경우 생성자에 의해 이루어짐

default constructor(기본 생성자)

 -  어떤 인자도 주어지지 않은 채로 호출될 수 있는 생성자

copy constructor(복사 생성자)

 - 
어떤 객체의 초기화를 위해 같은 타입의 객체로부터 초기화할때 호출되는 함수

copy assignment constructor(복사 대입 연산자)

 -  같은 타입의 다른 객체에 어떤 객체의 값을 복사하는 용도
class Widget {
 public:
    Widget();  //기본 생성자
    Widget(const Widget& rhs);//복사 생성자
    Widget& operator(const Widget& rhs);//복사 대입 연산자
   ...
}

Widget w1;   //기본 생성자 호출
Widget w2(w1); // 복사 생성자 호출
w1 = w2;       //복사 대입 연산자 호출

Standard Template Library(STL)
 -  말그대로 표준 템플릿 라이브러리

function object(함수 객체)
 -  함수처럼 동작하는 C++ 객체
 -  함수 호출 연산자인 operator()를 오버로드한 클래스로 만듬

undefined behavior(미정의된 동작)
 - 동작 자체가 정의되어 있지 않음
 - 실행 시간에 어떤 현상이 터질지 예측할 수 없음

int  *p = 0;  // p is null pointer
std::cout << *p; // 널 포인터를 역참조하면 미정의 동작발생

char name[] = "Darla"; //name은 크기가 6인 배열

char c = name[10]; // 유효하지 않은 배열 index로 참조하려
                               하면 미정의 동작 발생

interface(인터페이스)
 -  함수의 시그너처, 혹은 어떤 클래스의 접근 가능 요소, 템플릿의 타입 매개변수로서 유효해야 하는 표현식

client(사용자)

 -  코드를 사용하는 아무개 혹은 아무것.

by 일찍자라 | 2006/11/09 00:58 | Effective C++ | 트랙백

◀ 이전 페이지          다음 페이지 ▶