본문 바로가기

Programing/C++

[C++] Const, Return, Rvalue - Reference

728x90
반응형

 

전 포스팅에 이어서 Reference의 활용으로 Const, Return, Rvalue에 대해서 알아보자.

 

 

Const Reference

 

아래와 같은 예시가 있다고 생각해보자,

 

#include <iostream>

struct Rect
{
    int left;
    int top;
    int right;
    int bottom;
};

void foo(Rect r)
{
    
}

int main(int argc, const char * argv[]) {
    // insert code here...
    Rect rc = {1,1,5,5};
    foo(rc);
}

 

 

여기서 foo 함수에 Rect 타입 변수 rc를 전달하는데  "foo 함수 안에서는 rc의 상태는 변경되면 안된다."

라는 규칙이 적용이 되면 우리는 Call by value로 전달하면 된다.

 

Call by value는 인자로 전달된 변수를 수정하지 않겠다는 약속이 내포되어 있다. 하지만 구조체  같은 사용자 정의 타입을 인자로 사용 하는 경우 "복사본에 대한 오버헤드"가 있다.

 

 

이런 오버헤드를 제거 하기 위해 서는 참조를 사용 해야한다. 거기에 앞서 Call by value의 규칙이었던 foo함수 안에서는 rc상태가 변경되면 안된다 라는 규칙을 이행 하기 위해 Const 라는 keyword를 붙이면 된다. 

 

아래 예시를 보자.

 

#include <iostream>

struct Rect
{
    int left;
    int top;
    int right;
    int bottom;
};

void foo(const Rect& r)
{
    r.left = 100; // error (수정하지 않겠다고 const를 붙였는데 수정이 발생하면 에러가 발생함.
}

int main(int argc, const char * argv[]) {
    // insert code here...
    Rect rc = {1,1,5,5};
    foo(rc);
}

 

 

void foo(const Rect& r) 와 같이 const를 키워드를 앞에 붙여주고 reference 변수를 인자로 받으면 된다.

const를 붙인 상태에서 r.left = 100; 과 같이 값을 수정을 하려고 하면 error가 발생 한다.

 

이렇듯 const reference는 복사본에 대한 오버헤드 없이 인자로 전달된 변수를 수정하지 않겠다는 약속이다.

이는 성능 측면에서 아주 장점이 많은 방법이다. 이로 인해 C++에서 가장 널리 사용되는 인자 전달 방식이다.

 

 

정리하면 다음과 같다, 

 

1. 함수 인자의 값이 변경 하는 경우

 - 포인터나 레퍼런스 모두 가능

 - C++ 에서는 레퍼런스를 좀 더 많이 사용

 

pointer void swap(int*p1, int*p2)
reference void swap(int&r1, int&r2)

 

 

2. 함수의 인자의 값이 변경하지 않는 경우

※ 주의 할 점 : 인자의 타입에 따라 다르다.

 

user define type const reference 사용
f1(const Rect&r)
- 일반적으로 타입의 크기가 크다
- 메모리 사용량 증가
- 복사 생성자 호출의 오버헤드를 줄이기 위해
primitive type (내장타입) call by value
f1 (int n)
- 타입의 크지 않고, 생성자 개념이 없고, reference를 사용하는 것 
보다 더 많은 컴파일러 최적화가 지원된다.

 

 

 

Return by Reference

 

아래 예시를 보자, 

 

#include <iostream>

struct Point
{
    int x;
    int y;
};

void f1(Point pt) {}    // 복사본 생성
void f2(Point& pt) {}   // 복사본 생성 안함

Point pt = {0,0};
Point foo()  // 값 타입 반환, return by value
{
	return pt;
}

int main(int argc, const char * argv[]) {
    // insert code here...
    foo().x = 10;
}

 

위 코드에서 foo()함수 자리에는 위에저 전역 변수로 만든 pt 가 온 것일까? 

 

다들 햇갈릴거라고 생각 한다. 나도 처음에는 이해가 되지 않았다. 천천히 정리해보자.

먼저 아래에 대해 기억하자

Call by value
Return by value
복사본을 생성하지 않는다.
Call by reference
Return by reference
복사본을 생성하지 않는다.

 

 

foo함수를 보면 Point pt 형태로 되어있다 이는 값을 리턴한다는 의미로 return by value 라고 볼 수 있다.

즉 실제적으로 전역 변수 pt를 넘기는 것이 아니라 리턴용 임시 객체를 생성해서 반환 한다는 의미다.

 

또한 임시객체는 등호의 왼쪽에 있을 수 없다 그렇기 때문에 위 코드에서 foo().x = 10; 는 에러가 발생한다.

 

 

만약 전역 변수 pt를 반환 하기 위해서는 foo 함수에 &를 붙여 Return by Reference형태로 변경 하면 된다.

 

자 이제 수정된 코드를 작성하면 아래와 같다

#include <iostream>

struct Point
{
    int x;
    int y;
};

void f1(Point pt) {}    // 복사본 생성
void f2(Point& pt) {}   // 복사본 생성 안함

Point pt = {0,0};
Point& foo() {return pt;}

int main(int argc, const char * argv[]) {
    // insert code here...
    foo().x = 10;
}

 

 

이렇게 작성하면 에러 없이 comfile이 되는 것을 볼 수 있다.

 

예제를 통해 조금 더 알아보자.

 

#include <iostream>


int x = 10;

int f1() {return x;}
int& f2() {return x;}


int main(int argc, const char * argv[]) {
    // insert code here...
    f1() = 20; // 10 = 20 : error
    f2() = 20; // x = 20 : ok
    
    std::cout << x << std::endl;
    
}

 

 

f1() = 20; 구문에서 에러가 발생한다. 왜냐면 값을 리턴하게 되기 때문에 해당 코드를 변환 해보면 10=20 이라는 식이 되면서 문법에 맞지 않는 코드가 된다.

 

반대로 f2() = 20; 구문에서는 에러가 발생하지 않는다 x의 reference 변수를 넘겨주는 것으로 주소를 넘겨주기 때문에 변환 해보면 x=20이라는 식이 되면서 문법에 맞는 표현으로 정상 컴파일이 된다.

 

정리하면 다음과 같다, 

 

함수가 을 반환하면 리턴용 임시 객체가 반환 된다.
함수 호출 식이 lvalue가 될 수 없다
(등호의 왼쪽에 함수 호출식을 놓을 수 없다.)
참수가 참조를 반환하면 리턴용 임시 객체가 생성 되지 않는다
함수 호출식이 lvalue가 될 수 있다.

 

 

Rvalue Reference

 

아래 예시를 보자,

v1 = 10; 구문에서는 ok 이다 이렇게 오른쪽에만 올수 있는 인자를 rvalue라고 부른다. 반대로 오른쪽 왼쪽 모두다 올 수 있는 인자를 lvalue라고 부른다.

 

#include <iostream>

int main(int argc, const char * argv[]) {
    // insert code here...
    int v1 = 0, v2 = 0;
    
    v1 = 10; // ok,    10 : rvalue
    10 = v1; // error, v1 : lvalue
}

 

rvalue lvalue
등호(=) 오른쪽에만 올 수 있는 것 등호(=) 왼쪽과 오른쪽에 모두 올 수 있는 것

 

 

자 이제 reference를 사용해서 rvalue, lvalue를 사용 해보자.

 

아래 코드를 보자

#include <iostream>

int main(int argc, const char * argv[]) {
    // insert code here...
    int v1 = 0, v2 = 0;
    
    v1 = 10; // ok,    10 : rvalue
    // 10 = v1; // error, v1 : lvalue
    
    int& r1 = v1; // ok
    int& r2 = 10; // error
    
    const int& r3 = v1; //ok
    const int& r4 = 10; //ok
    
    int&& r5 = v1;   // error
    int&& r6 = 10;   // ok
}

 

 

int& r2 = 10; 이 구문에서 에러가 발생한다. 바로 주소를 받아와야할 reference 변수가 값을 할당을 받았기 때문에 error가 발생하는 것이다. 이렇듯 일반적인 reference 변수는 rvalue를 받으면 error를 발생 한다.

 

reference변수에 rvalue를 가르키게 하기 위해서는 위 코드에서 처럼 2가지 방법이 있다.

 

1. const 변수를 붙이면 값을 가르킬 수 있다. 

2. rvalue만을 가르키기 위한 참조 keyword로 &&를 붙이면 된다. 이것을 붙이게 되면 lvalue부분에서 error가 발생하는 것이다.

 

lvalue reference const lvalue reference rvalue refrence
lvalue만 가르킬 수 있다. lvalue와 rvalue 모두 가르킬 수 있다. rvalue만 가르킬 수 있다.

 

 

 

 

 

※ 참조 

 - ecourse.co.kr : c++ 초급

 

 

 

 

 

 

 

 

 

 

 

반응형