Skip to main content

[C++ Primer Plus] Chapter 8. 함수의 활용

C++ 기초 플러스 책을 읽고 공부한 노트입니다.




참조 변수 #

  • 실제 변수의 대용이름. 함수의 매개변수에 사용한다.
  • 포인터와 달리 초기화로만 참조를 설정할 수 있다.

  • 참조 매개변수가 const일 경우 다음 조건일 때 임시변수가 생성된다. 그래서 원본 데이터가 변하지 않는다.
    • (1) 올바른 데이터형이지만 lvalue가 아닐 때
    • (2) 잘못된 데이터형이지만 올바른 데이터형으로 변환할 수 있을 때
double RefCube( const double & ref ) 
{ 
    return ref * ref * ref; 
}

double dNum = 5.0;
long lNum = 5L;

RefCube( lNum );          // 2
RefCube( dNum + 5.0 );    // 1
RefCube( 7.0 );           // 1

  • rvalue 참조는 &&를 사용한다.
    • lvalue는 단일 표현식 이후에도 없어지지 않고 지속되는 객체이다.
    • rvalue는 표현식 종료 이후에는 더 이상 존재하지 않는 암시적인 값이다.
    • rvalue 참조는 Chapter 18에 나올 move semantics에 쓰인다.
double && rref1 = 13.0;        // 리터럴 상수
double && rref2 = 2.0 + dNum;  // 표현식
double && rref3 = sqrt(2.0);   // rvalue를 반환하는 함수



매개변수에 const 사용이 좋은 이유 #

  • 실수로 데이터형 변경을 막을 수 있다.
  • constconst가 아닌 매개변수를 모두 처리할 수 있다.
  • 참조로 매개변수가 전달되면 임시변수를 생성해서 사용할 수 있다.



구조체 참조 #

struct Student
{
    //...
}

Student& Func(Student& s)
{
    //...
} 

Student s1, s2;

// 대입 구문으로 값이 복사된다. 
s1 = Func(s2);   

// 만약 Func 함수의 리턴값이 const이면 안 되는 구문이다.
// 리턴 값이 const이면 상수 참조이기 때문이다. 
Func(s2) = s1;  

  • 구조체를 참조로 리턴하는 경우 전체 구조체를 임시 장소에 복사한 후 다시 복사하는 과정이 없고, 직접적으로 복사되기 때문에 보다 효율적이다.
  • 함수가 종료될 때 수명이 끝나는 임시 변수를 참조로 리턴하지 않도록 주의해야 한다.
    • 해결 방안은 매개변수로 전달된 참조를 리턴하거나, 포인터를 사용하는 방법이 있다.
struct Student
{
    //...
}

const Student& Func(Student& s) 
{     
    Student *ptr = new Student;   // delete를 까먹을 수 있다. (unique_ptr이 괜찮은 대안이다)
    *ptr = s; 
    
    return *ptr; // 포인터를 사용해서 구조체를 참조로 리턴한다. 
}



string 객체 참조 매개변수에 C 스타일 문자열 매개변수 전달하기 #

  • string 클래스가 char *형을 string으로 변환한다.
  • const 참조 형식의 매개변수라면 임시 변수를 만들게 된다.
void Func(string str) // str과 “Hello”는 서로 주소가 다르다.
{ 
    //...
}    

Func("Hello"); 



객체의 상속과 참조 #

  • ostream을 상속받는 ofstream형 객체들은 ostream & 참조형으로 받을 수가 있다.
ios_base::fmtflags initial;
initial = cout.setf(ios_base::fixed);  // 고정소수점 표기. 바꾸기 전 포맷팅 설정을 리턴한다. 
cout.precision(1);                     // 소수점 이하 숫자 개수
cout.setf(ios::showpoint);             // 0이더라도 소수점 표시
cout.width(17);                        // 필드 폭 설정 ( 한번 출력 후 초기화됨)
cout.setf(initial);                    // 이전 포맷팅 설정 복원



참조 매개변수를 사용하는 이유 #

  1. 전달되는 데이터 객체의 변경을 위해
  2. 객체 대신 참조를 전달해서 속도를 높이기 위해
  • 매개변수가 배열이라면 참조 매개변수는 배열의 크기를 구체적으로 명시해야 하므로, 포인터가 유일한 대안이겠다.



디폴트 매개변수 #

  • 함수의 원형에 명시한다.
  • 어떤 매개변수를 디폴트 매개변수로 만들려면, 그 오른쪽에 있는 모든 매개변수를 디폴트 매개변수로 만들어야 한다.
void Func(int one, int two = 2, int three = 3)
{
    //...
}

Func (1, ,3);  // (X) 매개변수를 건너뛸 수는 없다. 



함수 오버로딩 (함수의 다형) #

  • 서로 다른 여러 개의 함수가 하나의 이름을 공유하는 것.
  • 함수를 호출하면 매개변수 리스트(= 함수 시그내처)에 따라 알맞은 시그내처를 가진 함수원형을 찾아준다.
  • 리턴형이 달라도, 시그내처가 같으면 오버로딩이 불가능하다. 반면, 시그내처가 다르면 다른 리턴형을 가질 수 있다.
  • 함수 오버로딩이 되어서 여러 개의 함수가 존재하는데, 알맞은 시그내처가 없을 경우 컴파일 에러가 난다.
void Solution (int num) 
{ 
    //...
}

void Solution (float num)
{ 
    //...
}

double test = 5.0;
Solution(test);  // double형 매개변수를 가진 시그내처가 없으므로 컴파일 에러

  • 데이터형과 그 참조는 서로 같은 것으로 친다.
  • constconst가 아닌 변수는 구별된다.
  • 이름장식
    • 컴파일러는 오버로딩된 함수들의 이름을 알아보기 어려운 내부 형식으로 변환하여 암호화한다. 이것으로 서로 구분한다.



오버로딩 참조 매개변수 #

변경가능 lvalue ( x ) const lvalue ( 1 ) rvalue ( x + y )
void Solution ( int & num ); O X X
void Solution ( const int & num ); O O O
void Solution ( int && num ); X X O



함수 템플릿 #

// 함수 템플릿 원형. class 대신 typename을 사용해도 된다. 
template <class T>
void Swap (T & a, T & b);

// 함수 템플릿 정의
template <class T>
void Swap (T & a, T & b) 
{     
    //...
}



함수 템플릿의 제한 #

template <typename T> 
int Add(T a, T b) 
{ 
    return a + b; 
}
  • 이경우 Tstruct라면 작동하지 않는다. 대안은…?
  1. 연산자를 오버로드한다.
  2. 특별한 형에 대하여 특화된 템플릿을 정의한다. (명시적 특수화)



명시적 특수화 #

  • 다른 Swap() 템플릿을 사용하지 말고, 주어진 형에 맞게 특별히 명시적으로 정의된 이 함수 정의를 사용해라.
  • 조건
    • 매개변수 동일
    • 반환형 동일
    • 앞에 template <> 붙이기
struct Student
{
    //...
}

// <Student>에서 Student 생략가능.
template <> void Swap<Student>(Student & s1, Student & s2) 
{
    //...
}



암시적 구체화, 명시적 구체화 #

  • 구체화: 컴파일러가 특정 데이터형에 맞는 함수 정의를 생성하기 위해 템플릿을 사용한 결과.

  • Swap() 템플릿을 사용해서, 주어진 형에 맞는 함수 정의를 생성해라.

  • 암시적 구체화: 컴파일러에게 암시적으로 Swap() 템플릿을 사용해서, x, y 형에 맞는 함수 정의를 생성하라고 알린다.

  • 명시적 구체화: 컴파일러에게 명시적으로 Swap() 템플릿을 사용해서, int 형에 맞는 함수 정의를 생성하라고 알린다.

// 암시적 구체화
Swap(x, y);

// 명시적 구체화
template void Swap<int> (int, int);
Swap<int> (x, y);



컴파일러는 어떤 함수를 선택할까? #

  • 순서
    • 일반 함수 > 명시적 특수화 > 일반 템플릿
  • 가능한 함수가 둘 이상이면 모호하기 때문에 에러가 발생한다.
  • 사용자가 컴파일러에게 적절한 함수를 사용하도록 리드할 수 있다.
Swap<>(x, y);    // <>는 템플릿 함수를 선택해야한다는 것을 의미한다. 
Swap<int>(x, y); 



decltype 키워드 #

  • declared type의 줄임말이다.
    • 특정 인스턴스가 생성되기 전까지 타입을 결정할 수 없을 때 템플릿을 정의하는 데 사용하는 특별한 방법이다.
    • 내부 괄호로 한번 더 묶으면 그 안의 표현식을 lvalue참조로 본다.
int x = 1, y = 2;

decltype (x + y) xpy;          // x+y과 동일한 타입의 xpy를 만들어라        
xpy = x + y;

decltype (x + y) xpy = x + y;  // 위와 같은 구문

decltype ((x)) xr = x;         // int & 타입  
template<class T1, class T2>
auto Solution(T1 x, T2 y) -> decltype(x + y)  // 적절한 x + y의 리턴형을 선언할 수 있다.
{ 
    return x + y;
}