레이블이 const인 게시물을 표시합니다. 모든 게시물 표시
레이블이 const인 게시물을 표시합니다. 모든 게시물 표시

2016년 3월 17일 목요일

[Effective C++] 항목 3 : 낌새만 보이면 const를 들이대 보자!

포인터와 const

const 키워드는 들어갈 수 있겠다 싶은 곳이면 어디든 붙일 수 있다.
포인터 타입에 대해서는 포인터 자체를 상수로, 혹은 포인터가 가리키는 대상을 상수로, 혹은 둘 다 지정할 수 있고, 아무것도 지정하지 않을 수도 있다.
char greeting[] = "Hello";
char* p = greeting; // 비상수 포인터, 비상수 데이터
const char* p = greeting; // 비상수 포인터, 상수 데이터
char* const p = greeting; // 상수 포인터, 비상수 데이터
const char* const p = greeting; // 상수 포인터, 상수 데이터
const 키워드가 *의 왼쪽에 있으면 포인터가 가리키는 대상이 상수, 
const가 *의 오른쪽에 있으면 포인터 자체가 상수이다.
const가 *의 양쪽에 다 있으면 포인터가 가리키는 대상 및 포인터가 전부 상수이다.
포인터가 가리키는 대상을 상수로 만들 때 const 를 사용하는 스타일이 다를 수 있는데,
void f1(const Widget *pw);
void f2(Widget const *pw);
위의 두 함수의 매개 변수 타입은 동일하다.

STL과 const

STL 반복자(iterator)는 기본적인 동작 원리가 T* 포인터와 흡사하다.
std::vector<int> vec;
// ...
const std::vector<int>::iterator iter = vec.begin(); // iter는 T* const 처럼 동작합니다.
*iter = 10; // OK, iter가 가리키는 대상을 변경한다.
++iter; // 에러! iter는 상수

std::vector<int>::const_iterator cIter = vec.begin(); // cIter는 const T* 처럼 동작합니다.
*cIter = 10; // 에러! cIter가 가리키는 대상이 상수
++cIter; // OK, cIter를 변경한다.
const의 가장 강력한 용도는 함수 선언에 쓸 경우이다.
const는 함수 반환 값, 각각의 매개 변수, 멤버 함수 앞에 붙을 수 있고, 함수 전체에 대해 const의 성질을 붙일 수 있다.

함수 반환 값을 상수로 만들기

class Rational { ... };
const Rational operator*(const Rational& lhs, const Rational& rhs);
함수의 반환 값이 상수일 때, 다음과 같은 어처구니 없는 실수를 막아준다.
Rational a, b, c;
// ...
(a * b) = c; // 에러!
위와 같은 경우는 바로 알아챌 수 있겠지만, 다음과 같은 경우는 비교적 많이 일어난다.
if (a * b = c) ... // 타이핑 실수, 원래 비교하려고 했던건데...
위의 코드는 a와 b가 기본 제공 타입이었다면 문법 위반에 걸리는 코드이지만, 사용자 정의 타입이기 때문에 허용된다.

상수 멤버 함수

멤버 함수에 붙는 const 키워드는 “해당 멤버 함수가 상수 객체에 대해 호출될 함수이다” 라는 사실을 알려준다. 
이런 함수가 중요한 이유는 두 가지이다.
  1. 클래스의 인터페이스를 이해하기가 쉬워짐: 해당 클래스 객체의 상태를 변화시킬 수 없는 함수는 무엇이며, 변화시킬 수 있는 함수는 무엇인지 클래스 선언만 보고 바로 알 수 있다.
  2. 해당 클래스의 상수 객체를 사용할 수 있게 해줌: C++ 프로그램의 실행 성능을 높이는 핵심 기법중의 하나인 “상수 객체에 대한 참조자”를 쓰려면, 상수 멤버 함수가 준비되어 있어야 한다.
const 키워드가 있고 없고의 차이만 있는 멤버 함수들은 오버로딩이 가능하다.
class TextBlock {
public:
    // ...

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

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

private:
    std::string text;
};
위 처럼 선언된 TextBlock의 operator[]는 다음과 같이 쓸 수 있다.
TextBlock tb("Hello");
std::cout << tb[0]; // 비상수 멤버 호출

const TextBlock ctb("World");
std::cout << ctb[0]; // 상수 멤버 호출
--> const를 붙여 상수객체가 되었고, 상수객체의 멤버인 const 함수를 호출 한다. 
위 예제는 이해를 돕기위한 용도가 크고, 실제 프로그램에서 상수 객체가 자주 생기는 경우는 상수 객체에 대한 포인터 혹은 참조자가 함수의 인자로 전달될 때이다.
void print(const TextBlock& ctb)
{
    if (ctb.empty()) return;
    std::cout << ctb[0];
    // ...
}
또한 상수 객체와 비상수 객체의 쓰임새가 아래처럼 달라질 수 있다.
std::cout << tb[0]; // OK
tb[0] = 'x'; // OK
std::cout << ctb[0]; // OK
ctb[0] = 'x'; // 에러!
상수 객체의 반환 타입이 const char&이기 때문에 넷째줄에서 에러가 발생했다.
비상수 버전의 operator[]는 참조자를 반환해야 한다. 만약 그냥 char를 반환한다면, 다음 문장은 컴파일되지 않는다.
tb[0] = 'x';
기본 제공 타입을 반환하는 함수의 반환 값을 수정하는 일은 불가능하다. 만약 된다고 해도, ‘값에 의한 반환’을 수행하는 C++의 특성상 수정되는 값은 tb.text[0]의 사본이지tb.text[0]의 값이 아니다.

비트수준 상수성과 논리적 상수성

  • 비트수준 상수성(물리적 상수성): 멤버 함수가 const이려면 그 객체의 어떤 데이터 멤버(정적 멤버는 제외)도 바꾸지 않아야 함.
C++에서 정의하고 있는 상수성도 비트수준 상수성이고, 컴파일러 차원에서 비트수준 상수성을 지키는 것은 매우 쉽지만, 포인터가 가리키는 대상을 변경하는 경우, 컴파일러가 잡아내지 못한다.
class CTextBlock {
public:
    // ...

    // 부적절한(그러나 비트수준 상수성을 지키는 것처럼 보여져서 허용되는) 선언
    char& operator[](std::size_t position) const
    { return pText[position]; }
private:
    char *pText;
operator[] 내부 코드만 보면 pText를 바꿀 수 없다는 것은 확실하다. 그러나 다음과 같은 코드에서는
const CTextBlock cctb("Hello");
char *pc = &cctb[0];
*pc = 'J'; // OK, 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); // 에러!
        lengthIsValid = true; // 에러!
    }
    return textLength;
}
위 코드는 효율적인 측면을 고려하여 textLength의 값을 캐시해 놓는다.
그리고 사용자는 length()가 어떤식으로 구현되어 있던 간에 pText의 값이 변하지 않을 것을 알고 함수를 호출할 것이다.
이때 이 함수는 논리적 상수성을 지키고 있는 것이다.
그러나 컴파일러가 비트수준 상수성을 강제하고 있기 때문에 컴파일 에러가 발생한다.
이를 해결할 방법은 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;
}

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

TextBlock::operator[]가 기능이 확장되어(경계 검사, 접근 데이터 로깅, 무결성 검증 등) 코드가 비대해지면, 아무리 개별 함수로 빼낸다 해도 코드 중복이 생길 수 밖에 없다.
class TextBlock {
public:
    // ...

    const char& operator[](std::size_t position) const
    { 
        // 경계 검사
        // 접근 데이터 로깅
        // 무결성 검증
        return text[position];
    }

    char& operator[](std::size_t position)
    {
        // 경계 검사
        // 접근 데이터 로깅
        // 무결성 검증
        return text[position];
    }

private:
    std::string text;
};
코드 중복은 절대악이다. 컴파일 시간, 유지보수 시간, 바이너리 크기 부풀림 등…
캐스팅을 써서 위와 같은 코드 중복을 피할 수 있다.
캐스팅은 기본적으로 피해야할 기법이지만, 코드 중복이 더 해악이므로 캐스팅을 사용한다.
class TextBlock {
public:
    // ...

    const char& operator[](std::size_t position) const
    { 
        // 경계 검사
        // 접근 데이터 로깅
        // 무결성 검증
        return text[position];
    }

    char& operator[](std::size_t position)
    {
        return
            const_cast<char&>(
                static_cast<const TextBlock&>
                    (*this)[position]
            );
    }

private:
    std::string text;
};
첫번째 캐스팅인 static_cast는 무한 재귀 호출을 피하기 위해, 즉 상수 버전의 operator[]를 호출하기 위해 사용되었고, 두번째 캐스팅인 const_cast는 결과에서 const를 떼어내기 위해 사용되었다.
물론 C 스타일의 캐스팅을 사용하면 괄호 만으로도 끝나겠지만, C 스타일의 캐스팅 보다는 C++ 스타일의 캐스팅을 사용해야 한다(항목 27 참조).
비상수 멤버 함수에서 상수 멤버 함수를 호출하는 이유는 반대로 생각하면 간단해 진다.
만약 상수 멤버 함수 쪽에서 비상수 멤버 함수 쪽을 호출했다면, 상수 멤버 함수로써는 비트 수준 상수성이 간단하게 깨지게 되는 것이기 때문이다.


  • const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는 데 도움을 준다.
  • 컴파일러는 비트수준 상수성을 지켜야 하지만, 우리는 논리적인 상수성을 사용하여 프로그래밍해야 한다.
  • 상수 멤버 함수 및 비상수 멤버 함수가 서로 동일한 기능으로 구현되어 있을 경우, 비상수 버전이 상수 버전을 호출하도록 만들자.

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

아래와 같은 define 문이 있을 때,
#define ASPECT_RATIO 1.653
ASPECT_RATIO라는 이름은 심볼은 컴파일러에겐 전혀 보이지 않는다(컴파일러의 심볼 테이블에 들어가지 않음). 선행 처리자가 숫자 상수로 바꾸어 버리기 때문이다.
이 때문에 생길 수 있는 문제는 다음과 같다.
  • 컴파일 에러가 발생하면 ASPECT_RATIO라는 심볼보다는 1.653이라는 상수를 마주하게 될 것이므로, 버그를 찾기가 어려워 진다. (더군다나 ASPECT_RATIO가 정의된 파일이 프로젝트 내에 있지 않을 경우, 찾기가 더욱 곤란해 질 수 있음)
  • 심볼릭 디버거에서도 ASPECT_RATIO라는 심볼 대신에 숫자를 보여주므로 디버깅이 어려워 질 수 있다.
이 문제를 해결할 수 있는 방법은 매크로 대신 상수를 쓰는 것이다.
const double AspectRatio = 1.653; // 대문자로만 표기하는 이름은 보통 매크로에서 쓰는 것이라서, 이름 표기도 바꿔줌
AspectRatio컴파일러의 심볼 테이블에 들어가게 된다. 
또한 추가로 얻을 수 있는 이점은 컴파일된 코드의 크기가 작아질 수 있다는 것인데, #define을 쓸 경우 사용된 개수만큼 해당 숫자의 사본이 생기게 되는데, 상수의 경우는 사본이 딱 한개만 생기기 때문이다(몇몇 CPU 아키텍쳐에서는 작은 정수 값에 대해서 Instruction Code 내부에 Immediate 타입의 값을 직접 저장할 수 있으므로, 해당이 되지 않을 수 있음).
#define 을 상수로 교체하려는 경우, 두가지 경우만 조심하자.
  1. 상수 포인터를 정의하는 경우: 보통 헤더 파일에 넣는 것이 관례이므로, 포인터는 꼭 const로 선언해 주어야 하고, 포인터가 가리키는 대상까지 const로 선언해 주어야 한다.
  2. const char* const authorName = "Scott Meyers"; // const의 의미와 사용법에 대한 자세한 사항은 항목 3 참조
    문자열 상수에는 char*같은 구닥다리 문자열 보다는 string 객체가 더 사용하기 편하다.
    const std::string authorName("Scott Meyers");
  3. 클래스 상수를 정의하는 경우: 어떤 상수의 유효범위를 클래스로 한정하고자 할 때, 그 상수의 사본 개수가 한개를 넘지 못하게 하고 싶다면 정적(static) 멤버로 만들어야 한다.
  4. class GamePlayer {
    private:
        static const int NumTurns = 5; // 상수 선언(declaration)
        int scores[NumTurns];
        // ...
    };
C++ 에서는 대부분의 것들에서 정의가 마련되어 있어야 하지만, 정적 멤버로 만들어지는 정수류(각종 정수 타입, charbool 등) 타입의 클래스 내부 상수는 예외이다.
이들에 대해 주소를 취하지 않는 한, 정의 없이 선언만 해도 아무 문제가 없다.
단, 클래스 상수의 주소를 구해야 한다면 얘기가 달라진다.
const int GamePlayer::NumTurns;
이 클래스 상수의 정의는 구현 파일에 두어야 한다. 또한 클래스 상수의 초기값은 해당 상수가 선언된 시점(헤더 파일)에 바로 주어지기 때문에 정의(구현 파일)에는 초기 값을 주지 않는다.
상수의 주소를 구한다거나, 상수의 참조자를 취하는 일을 막으려면 enum을 쓰면 된다.
class GamePlayer {
private:
    enum { NumTurns = 5 };
    int scores[NumTurns];
    // ...
};
const의 주소를 구하는 것은 합당하지만, enum의 주소를 구하는 것은 안되기 때문이다. 
enum은 어떠한 형태의 쓸데없는 메모리 할당도 절대 저지르지 않는다.


매크로 함수 대체 하기 
#define을 잘못 사용하는 경우는 종종 매크로 함수에서 볼 수 있다.
// 매크로 함수의 인자는 항상 괄호로 싸서, 표현식이 변형되는 것을 막아주자.
#define CALL_WITH_MAX(a, b) func((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a가 두 번 증가
CALL_WITH_MAX(++a, b+10); // a가 한 번 증가
이처럼 표현식의 결과에 따라 인자가 평가되는 횟수가 달라진다.
C++ 에서는 기존 매크로의 효율을 그대로 유지하면서 정규 함수의 모든 동작방식 및 타입 안전성까지 완벽하게 취할 수 있는 방법이 있다. 
바로
인라인 템플릿 함수(항목 30 참조)를 만드는 것이다.
template<typename T>
inline void callWithMax(const T& a, const T& b) // T가 정확히 어떤 타입인지 모르기 때문에, 상수 객체에 대한 참조자를 씀. (항목 20 참조)
{
    f(a > b ? a : b);
}
함수 본문에 지저분하게 괄호를 넣을 필요도 없고, 인자를 여러 번 평가하지도 않는다.
뿐만 아니라 진짜 함수이기 때문에, 유호범위 및 접근 규칙을 그대로 따라간다.
임의의 클래스 안에서만 쓸 수 있는 인라인 함수가 가능하다는 얘기다.
  • 단순한 상수를 쓸 때는, #define 보다 const 객체 혹은 enum 을 우선 생각하자.
  • 함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수를 우선 생각하자.

2016년 3월 9일 수요일

[c++] call by value(값에 의한 호출) & call by reference(참조에 의한 호출), 복사 손실, Reference to const

Call By Value(복사에 의한 함수 호출): 인자로 넘기는 값을 복사해서 새로운 함수에 넘겨주는 방식
값의 복사에 의한 함수 호출을 의미합니다. 
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. void swap(int a, int b);  
  5.   
  6. int main()  
  7. {  
  8.     int val1 = 10;  
  9.     int val2 = 20;  
  10.     swap(val1, val2);  
  11.   
  12.     cout<<"val1 : "<<val1<<endl;  
  13.     cout<<"val2 : "<<val2<<endl;  
  14.   
  15.     return 0;  
  16. }  
  17.   
  18. void swap (int a, int b)  
  19. {  
  20.     int temp = a;  
  21.     a = b;  
  22.     b = temp;  
  23.   
  24.     cout<<"a : "<<a<<endl;  
  25.     cout<<"b : "<<b<<endl;  
  26. }  


 위의 예제는 Call by Value 예제와 결과값 입니다. Call by Value는 값을 복사해서 전달하기 때문에
원본의 값이 변경될 가능성이 없다는 특징을 가지고 있지만, 값을 넘겨 줄때마다 고비용 , 복사손실 문제가 발생하는 단점도 있습니다. 
 ※ Call by Value는 Pass by Value 라고 불리기도 합니다. 

Call by Reference(참조에 의한 호출): 주소 값을 인자로 전달하는 함수 호출 
아래 예제를 보시죠. (위와 거의 유사합니다.)
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. void swap(int *a, int *b);  
  5.   
  6. int main()  
  7. {  
  8.     int val1 = 10;  
  9.     int val2 = 20;  
  10.   
  11.     cout<< "Before the swap function" <<endl;  
  12.     cout<<"val1 : "<<val1<<endl;  
  13.     cout<<"val2 : "<<val2<<endl;  
  14.   
  15.     swap(&val1, &val2);  
  16.   
  17.     cout<<endl<<"After Swap function " <<endl;  
  18.     cout<<"val1 : "<<val1<<endl;  
  19.     cout<<"val2 : "<<val2<<endl;  
  20.   
  21.     return 0;  
  22. }  
  23.   
  24. void swap (int *a, int *b)  
  25. {  
  26.     int temp = *a;  
  27.     *a = *b;  
  28.     *b = temp;  
  29. }  
  위의 예제에서 알 수 있듯이 Call by Reference는 복사손실과 고비용 문제에서는 벗어났지만, 원본의 값의 변경이 일어 날 수 있다는 중대한 문제점을 안고 있는걸 볼 수 있습니다. 

 Call by Value 나 Call by Reference 둘다 문제점을 가지고 있는데요, 



[항목 20] Effective C++ : 값에 의한 전달보다는 상수 객체 참조자에 의한 전달 방식을 택하는 편이 대게 낫다.

Pass by Value(Call by value) 의 문제점 
   1. 고비용 문제 
     - 해결 방안 : const,. 상수 객체에 대한 참조자로 전달 하는 방법 
   2. 복사손실 (slicing problem) 문제
     - 해결 방안 : 


  기본적으로 C++는 함수로부터 객체를 전달받거나 함수에 객체를 전달할 때 값에 의한 전달 방식을 사용하는데요. 특별히 다른 방식을 지정하지 않는 한, 함수 매개변수는 실제 인자의 '사본'을 통해 초기화되며, 어떤 함수를 호출 한 쪽은 그 함수가 반환한 값의 사본을 돌려받습니다. 이들 사본을 만들어 내는 곳이 바로 복사 생성자인데, 이런 점 때문에 고비용의 연산이 되기도 합니다.

  한번 예제를 보면서 알아 보도록 하겠습니다. 
  1. #include <iostream>  
  2. #include <string>  
  3. using namespace std;  
  4.   
  5. class person{  
  6. public:  
  7.     person() : m_strName("name"), m_strAddress("address")  
  8.     {  
  9.         cout<<"person default con call"<<endl;  
  10.     }  
  11.     person(const person& rhs)  
  12.     {  
  13.         this-> m_strName = rhs.m_strName;  
  14.         this-> m_strAddress = rhs.m_strAddress;  
  15.   
  16.         cout<<"person copy con call"<<endl;  
  17.     }  
  18.     virtual ~person()  
  19.     {  
  20.         cout<<"~person call "<<endl;  
  21.     }  
  22. protected:  
  23.     string  m_strName;  
  24.     string  m_strAddress;  
  25. };  
  26.   
  27. class student :public person  
  28. {  
  29. public:  
  30.     student() : m_strSchoolName("school")     
  31.     {     
  32.         cout<<"student default con call"<<endl;   
  33.     }  
  34.     student (const student& rhs)  
  35.     {  
  36.         this ->m_strName = rhs.m_strName;  
  37.         this->m_strAddress = rhs.m_strAddress;  
  38.         this->m_strSchoolName = rhs.m_strSchoolName;  
  39.         cout<<"student copy con call"<<endl;  
  40.     }  
  41.   
  42.     ~student()  
  43.     {  
  44.         cout<<"~student call"<<endl;  
  45.     }  
  46. private:  
  47.     string m_strSchoolName;  
  48. };  
  49.   
  50. bool validatestudent (student stu); //student를 값으로 전달받는 함수  
  51.   
  52. int main()  
  53. {  
  54.     student stu;  
  55.   
  56.     cout<<"-------- pass by value ----------"<<endl;  
  57.     validatestudent(stu);  
  58.     cout<<"---------------------------------" <<endl;  
  59.   
  60.     return 0;  
  61. }  
  62.   
  63. bool validatestudent(student stu)  
  64. {  
  65.     return true;  
  66. }  
실행결과 :


 위의 예제를 실행해 보면 Pass by Value방식에는 문제가 없습니다. 실행을 해보면 생성자와 소멸자가 제대로 실행되는게 보입니다. 하지만, student 객체 생성시 student 객체에는 string 객체 두개가 멤버로 들어 있기 때문에, cout으로 명시적으로 보여준 생성자 , 소멸자 생성, 소멸의 과정 뿐만 아니라, student 객체가 생성될때마다 이들 string 객체 두개도 덩달아 생성되고 소멸이 되고, 이때마다 생성자, 소멸자를 호출 해야 하므로, 고비용의 문제가 발생하게 됩니다. 

그래서 이런 고비용 문제를 피하기 위한 방법이 있습니다. 바로 "상수 객체에 대한 참조자"로 전달하는 방법입니다. (Reference to const바로 Call by Reference 방법에 상수화를 더한 것이죠. 

방법은 bool validatestudent부분에서 괄호 부분을 (const student &stu); 이렇게 바꾸면 됩니다.이 방식의 좋은점은 새로 만들어지는 객체 같은 것이 없기 때문에, 생성자와 소멸자 호출도 당연히 없습니다.(Call by Reference의 장점과 같죠) 이로서 고비용의 부담문제를 해결 할수 있습니다. 

또한 여기서 주목해야 할 점은 const 상수 선언인데, 그 전에  call by value와 call by referecne가 가진 특징을 다시 한번 상기 시켜 봅시다. 

 우리가 사용하려는 참조자에 의한 전달 방법으로 고비용 문제는 해결했지만, 원본값이 변경될 우려가 있으므로, const를 씀으로서 이런 변화로부터 안전하게 보호를 해주는 것입니다. 

두번째 문제는 바로 복사손실 (slicing problem) 문제 입니다. Call by Reference를 씀으로 인해서 바로 복사손실을 방지할 수 있는 장점이 있는데요. 우선 예제 코드를 보겠습니다. 예제 코드는 윈도우 클래스로 만들어지는 객체는 이름을 갖고 있고, 이 이름은 name 멤버 함수로 얻어내고,  display 함수로 이를 화면표시를 해주는 예제 인데요. 
  1. #include <iostream>  
  2. #include <string>  
  3. using namespace std;  
  4.   
  5. class window  
  6. {  
  7. public:  
  8.     window()  
  9.     {  
  10.         cout<<"window class construcgtor call "<<endl;  
  11.         window_name = "window is shit";  
  12.     }  
  13.     string name() const  
  14.     {  
  15.         cout<<"window::name() call "<<endl;  
  16.         return window_name;  
  17.     }  
  18.     virtual void display() const  
  19.     {  
  20.         cout<<"-------------------------"<<endl;  
  21.         cout<<"window::display() call "<<endl;  
  22.         cout<<"-------------------------"<<endl;  
  23.   
  24.     }  
  25.     ~window()  
  26.     {  
  27.         cout<<"~window() call" <<endl;  
  28.     }  
  29. private:  
  30.     string window_name;  
  31. };  
  32.   
  33. class wnidowwithscrollbar :public window  
  34. {  
  35. public:  
  36.     wnidowwithscrollbar()  
  37.     {  
  38.         cout<<"wnidowwithscrollbar() call"<<endl;  
  39.     }  
  40.     void display() const  
  41.     {  
  42.         cout<< "------------------------"<<endl;  
  43.         cout<<"wnidowwithscrollbar :: display()"<<endl;  
  44.         cout<<"------------------------"<<endl;  
  45.   
  46.     }  
  47.     ~wnidowwithscrollbar ()  
  48.     {  
  49.         cout<<"~wnidowwithscrollbar() call "<<endl;  
  50.     }  
  51. };  
  52.   
  53. void print_name_and_diplay(window w) //윈도우의 이름을 출력하고 그 윈도우를 화면에 표시 하는 예제 함수입니다.   
  54. {  
  55.     cout<<"------ print name and display ---------"<<endl;  
  56.     cout<<w.name()<<endl;  
  57.     w.display();  
  58. }  
  59.   
  60. int main()  
  61. {  
  62.     wnidowwithscrollbar wsb;  
  63.     print_name_and_diplay(wsb);  
  64.   
  65.     return 0;  
  66. }  
 위 함수들을 파생클래스 객체를 넘긴다면 , 매개변수 w가 생성되기는 하지만, 어떤 타입의 객체가 넘어가든 window 클래스 객체의 면모만을 갖게 되므로, 가상함수를 사용했음에도 불구하고 windowwithscroolbars::display는 출력되지 않는 것이 바로 문제가 됩니다. 여기도 마찬가지로 문제를 해결하기 위해서는 상수 객체에 대한 참조자로 전달 하도록 만들면 됩니다. 
  1. void print_name_and_diplay(const Window &w)  
이렇게 만들면 어떠한 객체 타입이 넘어와도 그 고유 성질을 그대로 갖게 되므로 복사손실은 발생하지 않습니다. 

항목 20의 제목에서 나타난거와 같이 상수객체 참조자에 의한 전달방식이 택하는 편이 고비용, 복사손실 두개의 문제해결능력으로 봐서는 대게는 나은 결과를 보여주지만, 기본제공 타입, STL반복자 그리고 함수 객체 타입, 이 세가지는 이전부터 값으로 전달되도록 설계해 왔기 때문에 이 두가지 경우에는 pass by Value 방법을 사용하는 것이 더 적절합니다. 

 ※ 상황에 따라 적절하게 Call by Value , Call by Reference를 쓰자~!!!



6-3-라.C++의 참조호출

C언어는 포인터를 사용하여 참조 호출 흉내를 낸다. C++은 포인터를 이용하는 방법외에 레퍼런스라는 개념으로 참조 호출을 추가로 지원한다. 다음 예제의 plusref2함수가 C++의 레퍼런스를 사용하여 참조 호출을 하는 예이다.

  : CallRef2
#include <Turboc.h>

void plusref2(int &a);

void main()
{
     int i;

     i=5;
     plusref2(i);
     printf("결과=%d\n",i);
}

void plusref2(int &a)
{
     a=a+1;
}

int &a라는 좀 이상한 타입을 사용하는데 이것이 레퍼런스이다. 호출부에서는 &i가 아닌 i를 전달하며 함수의 본체에서는 *a를 쓰지 않고 a를 바로 쓴다. C++의 레퍼런스에 대해서는 15장에서 상세하게 다루므로 여기서는 구경만 해 보고 넘어가기로 하자. 지금 단계에서 레퍼런스를 논하는 것은 순서에 맞지 않다. C++이 참조 호출을 위해 레퍼런스라는 새로운 개념을 지원하기는 하지만 C의 포인터를 이용한 참조 호출도 알아 두어야 한다.
C언어의 포인터를 통한 참조 호출은 사실 엄밀한 의미의 참조 호출이라고 볼 수 없다. 왜냐하면 이때도 함수로 전달되는 것은 변수 그 자체가 아니라 변수의 번지값(Address Value)이라는 특수한 값이기 때문이다. C는 언제나 값만 전달한다. 다만 이 번지값으로 실인수를 조작할 수 있도록 흉내만 낼 수 있으며 흉내를 통해 참조 호출 효과가 발생할 뿐이다. 그래서 혹자는 C언어는 참조 호출을 지원하지 않는다고 주장한다. 이는 문법적으로는 사실이지만 실제로는 포인터를 통해 참조 호출과 동일한 효과를 낼 수 있으므로 다소 비약적인 표현이라고 할 수 있다.

C언어의 함수 호출방식은 모두 값 호출 방식이되 편의상 포인터를 통한 호출을 참조 호출이라고 부를 뿐이다. 그렇다면 C++의 경우는 어떠할까? C++의 레퍼런스는 C의 포인터를 사용하는 방법보다는 더 발전된 방법이지만 이것도 엄밀하게 따지면 값 호출이다. 다음에 레퍼런스를 깊게 배워 보면 알겠지만 레퍼런스는 내부적으로 포인터를 흉내낸다. 따라서 레퍼런스를 사용한 참조 호출은 포인터를 흉내내어 참조 호출을 흉내내는 아주 기만적인 참조 호출일 뿐이다.


[Effective C++] 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해 두자.

인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해집니다. 인라인 함수의 아이디어는  함수 호출문을 그 함수의 본문으로 바꿔치기하자는 것  남발했다가는 코드의 크기가 커질 게 뻔하다. 인라인 함수로 부풀려진 ...