C++에서는 함수로부터 객체를 전달받거나 함수에 객체를 전달할 때 '값에 의한 전달' 방식을 사용합니다.

함수 매개변수는 실제 인자의 사본을 통해 초기화되며, 어떤 함수를 호출한 쪽은 그함수가 반환한 값의 사본을 돌려 받습니다.

 

위의 내용처럼 사본을 만들어내는 원천이 바로 복사 생성자 입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person 
public : 
    Person(); 
    virtual ~Person(); 
 
private
    std::string name; 
    std::string address; 
}; 
 
class Student : public Person 
public : 
    Student(); 
    ~Student();
 
private : 
    std::string schoolName; 
    std::string schoolAddress; 
}; 
 
bool ValidateStudent( Student s ); 
 
Student plato;
 
bool platoIsValid = ValidateStudent( plato );

 

 

plato로부터 매개변수 s를 초기화시키기 위해 Student의 복사 생성자가 호출된다. s는 ValidateStudent가 복귀할 때 소멸된다. Student 객체가 생성될 때마다 string 멤버도 생성된다. 게다가 Student 객체는 Person 객체로부터 파생되었기 때문에, Student 객체의 생성자가 호출되기 이전에 Person 객체가 생성된다. Person 객체가 생성될 때에도 string 멤버도 생성된다.

이처럼 Student 객체를 값으로 전달하는 데 날아간 비용을 보면, 생성자 6번 소멸자 6번입니다.
 
위와 같은 방식의 문제점을 해결하기 위해서는?

 

bool ValidateStudent( const Student& s ); //참조의 의한 전달 방식으로 바꾸기만 하면 됩니다. 

 

이렇게 할 경우,

 

우선 새로 만들어지는 객체라는 것이 없으므로 생성자/소멸자가 호출되지 않는다.

그리고 const를 붙임으로써 전달인자로 보낸 객체가 함수에 의해 변하지 않는다는 것을 명시해준다. 원래 값에 의한 전달 방식때도 원본 객체는 보호되는데 그 의미를 그대로 적용한 것이라고 보면 됩니다.

 

또한, '복사 손실' 문제도 없어집니다. 아래의 코드를 봅시다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Window 
public : 
    std::string GetName() const
    virtual void Display() const
}; 
 
class WindowWithScrollBars : public Window 
public :  
    .... 
    virtual void Display() const
}; 
 
void PrintNameAndDisplay( Window w ) 
    std::cout << w.GetName(); 
    w.Display(); 
 
WindowWithScrollBars wwsb;
 
PrintNameAndDisplay( wwsb );
 
 
위의 코드는 wwsb 객체가 PrintNameAndDisplay() 함수의 인자로 전달될때 값의 의한 전달을 하게되는데, 이렇게 되면 WindowWithScrollBars 객체의 구실을 할 수 있는 부속 정보가 전부 싹뚝 잘립니다.
PrintNameAndDisplay() 함수안에 w는 어떤 타입의 객체가 넘겨지든 아랑곳없이 Window클래스 객체의 면모만을 갖게 됩니다. 그래서
PrintNameAndDisplay() 함수내에서 w.Display(); 는 Window::Display() 일것니다.
 
즉! 이런 '복사 손실' 을 방지하려면
void PrintNameAndDisplay( const Window& w );
 
참조의 의한 전달로 바꾸시면 됩니다.
 

참조자는 보통 포인터를 써서 구현됩니다. 즉, 참조자를 전달한다는 것은 결국 포인터를 전달한다는 의미인데,

전달하는 객체 타입이 기본제공 타입(int, char 등) 이라면 참조자보다는 값에 의한 전달을 하는 쪽이 더 효율적일 때가 많다.

 

기본제공 타입은 대개 크기가 작은 편이다.

그러나! 타입 크기가 작다고 해서 전부 값에 의한 전달이 효율적이지는 않다.

한개의 포인터 멤버만 가지고 있는 객체가 있다면(STL 컨테이너가 대표적) 이 객체를 복사할 때 포인터가 가리키는 대상까지 복사하는 작업도 따라다녀야 해서 오히려 비용이 더 들 수 있게 된다.

 

객체 크기도 작고 복사 생성자도 간단하게 만들어졌다 해도, 일부 컴파일러에서 기본제공 타입과 사용자 정의 타입을 다르게 취급할 수 있기 때문에(레지스터에 들어가는지 안들어가는지의 차이가 존재한다) 수행 성능에 있어서 차이를 보일 수 있다.

 

또한 사용자 정의 타입은 크기 변화에 언제든 노출이 되어있다. 지속적으로 추가 될 수 있는 부분이 있다는 말입니다.

 

이것 만은 잊지 말자!

◆ '값의 의한 전달' 보다는 '상수 객체 참조자에 의한 전달'을 선호 합시다. 대체적으로 효율적일 뿐만 아니라 복사 손실 문제까지 막아 줍니다.

◆ 이번 항목에서 다룬 법칙은 기본제공 타입 및 STL 반복자, 그리고 함수 객체 타입에는 맞지 않습니다. 이들에 대해서는 '값에 의한 전달'이 더 적절합니다.



+ Recent posts