이 영역을 누르면 첫 페이지로 이동
시간의화살 블로그의 첫 페이지로 이동

시간의화살

페이지 맨 위로 올라가기

시간의화살

행복하세요

[C++] OOP (2) 함수 오버로딩 / 함수 호출

  • 2022.09.28 13:40
  • Programming Language/C++

 

 

 

C++의 메모리 모델은 C와 동일하다.

간단하게 관련 문법을 살펴보자.

 

RHS : Right Hand Side expression

LHS : Left Hand Side expression

 

&x : RHS에서 x의 주소를 반환한다. (x값이 저장된 메모리 주소를 반환한다)

&x : LHS에서는 Reference Type을 의미한다.

*x : RHS에서 x가 가리키는 주소의 값을 반환한다. (x값으로 메모리 주소를 가진다. x값이 가리키는 주소에 할당된 값을 반환한다)

*x : LHS에서 RHS의 값을 x가 가리키는 주소에 값으로 할당한다. (x값으로 메모리 주소를 가진다. x값이 가리키는 주소에 RHS 값을 할당한다)

 

 

 

Const

 

 

기본 개념은 이쪽을 참고하자. (https://13months.tistory.com/228)

 

const int x = 16;
int *y = &x;

 

const int x = 16; // x의 주소가 100번지라고 하자. 

100번지에는 16이 저장돼있다.

 

const 키워드로 인해 100번지의 값은 수정되지 않는다.

 

const int x는 readonly 속성을 가진다.

int *y = &x; 는 x를 수정할 수 있는 가능성이 있어 컴파일 시점에서 에러를 뱉는다.

 

생각해보면 당연하다. 

int *y = &x;로 선언하면 *y는 const가 아니라서 마음대로 수정할 수 있고, 이에 따라 const int x 의 값도 수정될 수 있으니 컴파일 에러를 뱉어야 한다.

 

const int x = 16;
const int* y = &x;
*y = 10;

 

const int* y 는 y에 주소값이 저장돼있고, y에 저장된 주소값을 찾아간 값을 변경할 수 없음을 의미한다.

 

const int* y = &x; 까지는 오류를 뱉지 않는다. 어차피 수정할 수 없으니까.

*y = 10; 에서 y의 값을 수정하려 할 때는 당연히 오류를 뱉는다.

 

int x = 16;
const int* y = &x;
x = 10;
*y = 10;

 

x = 10 까지는 오류를 뱉지 않는다.

 

*y = 10에서 컴파일 오류가 발생한다.

y에 저장된 주소를 찾아간 값을 변경할 수 없어야 하는데, 변경을 시도했기 때문이다.

 

	int x = 1;
	int y = 2;
	int* const z = &x;

	z = &y;

 

int* const z 는 z에는 주소값이 저장돼있고, 주소값 자체를 변경할 수 없음을 의미한다.

 

z가 const 속성을 가져 y = &z에서 컴파일 오류를 뱉는다.

 

	int x = 1;	
	int* const z = &x;

	*z = 10;

 

 

z는 const지만 *z는 const가 아니다. 오류를 뱉지 않는다.

 

 

 

클래스 단에서 const를 살펴보자.

 

 

class Car{
	public:
		Car(std::string name) : name_(name){}
		const std::string* name() {return &name_;}
		void set_name(const std::string name) {name = name + "a";} // const라서 컴파일 오류
		void Something() const {name_ = name_ + "d";} // const는 모든 멤버변수에 적용된다.
	
	private:
		std::string name_;		
}

 

 

1. 리턴 타입으로 const

리턴 값이 immutable하다. 여기서는 const pointer를 반환하기 때문에 string* asfd = car1.name() 처럼 값을 할당받을 수 없다. (수정될 가능성이 있기 때문)

 

2. 파라미터 타입으로 const

파라미터를 immutable로 설정한다.

인자로 들어오는 값이 immutable 하게 설정된다.

 

3. 함수 시그니처 이후 const

모든 멤버변수들이 const로 설정된다. 

이미 멤버변수를 const로 설정해도 충돌이 발생하지 않고, 실수를 방지하기 위해 사용한다.

 

바뀌지 않는 요소에는 const를 붙여주자. 실수를 방지할 수 있고 여러 사람들과 협업 시 const키워드가 매우 중요해진다.

클래스 자체에 public과 같은 접근제어자와 const 키워드를 붙이지는 않는다. (자바와 다름)

 

 

 

Shallow Copy (얕은 복사)

 

 

값 까지 복사하지 않고 주소만 복사하는 경우를 말한다.

객체 하나의 값을 변경했는데 다른 객체까지 영향을 주는 경우가 생긴다.

cpp 컴파일러가 기본으로 지원하는 copy constructor가 shallow copy를 제공한다.

 

 

 

Deep Copy (깊은 복사)

 

 

값 까지 복사해 독립적으로 동작하도록 한다.

 

 

 

deep copy는 기본 constructor로 제공해주지 않으니 필요 시 직접 만들어야 한다.

 

shallow와 deep은 상대적인 개념이지만..

몇 다리를 건너서 복사하든 실제 데이터를 복사한다면 deep copy로 생각할 수 있다. 

실제 데이터가 복사되는지, 아니면 데이터의 주소까지만 복사하는지를 판단 기준으로 정하자.

 

 

		Car(const Car& car) : name_(car.name_) {} // Shallow Copy
		
		Car(const Car& car){
			name_ = new char[sizeof car.name_];
			strcpy(name_, car.name_);
		} // Deep Copy

 

 

 

 


 

 

 

 

 

인자를 메서드로 넘기는 방법에는 세 가지가 있다.

 

1. Call by values

2. Call by pointers

3. Call by references

 

 

1. Call by values

 

void Add(int y){
	y += 3;
}

int main() {

	int x = 1;
	Add(x);
}

 

 

 

 

인자의 값을 복사해서 메모리에 새로 할당한다.

여기서 할당하는 메모리는 Stack 메모리이기 때문에 함수가 끝나면 사라진다. (위의 그림에서 y가 사라진다)

 

 

 

코드를 차근차근 따라가보자.

 

Car car1("mycar") 을 실행해 메모리에 car1을 할당했다.

ChangeCarName(car1, "yourcar") 메서드를 실행했다.

Car car2 = car1 이 실행되고, Copy Constructor가 호출된다.

car2가 새롭게 메모리에 할당되고, car2의 name_은 car1의 값을 복사한 값이 됐다.

car2.set_name(name)으로 car2의 이름이 "yourcar"로 바뀌었다. (car1의 이름은 그대로이다.)

 

ChangeCarName 메서드가 종료된 후 스택에서 제거되기 때문에 car2 객체 또한 사라진다.

 

 

 

 

2. Call by pointers

 

void Add(int* y){
	if(y != NULL) *y += 3;
}

int main() {

	int x = 1;
	Add(&x);
}

 

 

인자로 변수의 주소를 넘겨준다.

메서드에서는 넘어온 인자가 NULL인지 아닌지를 확인하는 부분이 있다.

 

 

 

주소를 넘겨 연산하기 때문에 함수가 끝나면 함수가 스택에서 제거돼 변수 y가 사라지지만, 연산 결과는 유지된다.

 

 

 

 

 

3. Call by references

 

 

void Add(int& y){
	y += 3;
}

int main() {

	int x = 1;
	Add(x);
}

 

 

 

인자로 레퍼런스를 받는다.

여기서 레퍼런스는 같은 주소를 가지는 변수를 말한다. (그림 참고)

 

연산 결과가 유지된다는 점에서 Call by pointers와 비슷하지만, NULL 체크를 할 필요가 없는 점에서 다르다.

 

포인터를 넘길 때는 값이 NULL일 수도 있지만, 레퍼런스는 해당 변수를 그대로 사용하기 때문에 NULL일 수 없다.

 

객체가 넘어갈 때 NULL일 가능성이 있으면 Call by references를 사용하고, 구글은 Call by references를 사용할 때는 읽기 전용으로 사용함을 권장한다.

 

함수를 통해 지역 변수를 반환할 때 주소나 레퍼런스를 반환 시 Dangling Pointer를 사용하게 될 수 있으니 주의하자!!

 

 

 

Call by values를 사용하면 값을 모두 복사해야 해서 비용이 많이 들 때가 있다.

이런 경우 Call by pointers 혹은 Call by references를 사용하는 편이 합리적이다.

 

 

new 연산자 (C에서의 malloc) 를 사용해 객체를 생성할 경우 heap 메모리에 저장되고, 진행 중인 메서드가 끝나더라도 값이 사라지지 않는다.

 

자바에서는 거의 모든 객체를 new 연산자를 사용해서 생성하지만, C++에서는 객체를 생성하는 방법이 여러 가지가 있으니 적절하게 사용하도록 하자.

 

 

 

 

 


 

 

 

 

 

C++에서 함수 오버로딩의 가능 여부는 함수의 signature에 따라서 결정된다.

 

Signature : 함수 이름 / 파라미터 타입

Descriptor : 함수 이름 / 파라미터 타입 / 리턴 타입

 

오버로딩된 여러 함수를 사용할 때 어떤 함수가 사용될지는 컴파일 시점에서 결정된다. (static dispatch)

 

 

 

그럼 컴파일러의 선택 기준에 대해 알아보자.

위와 같이 작성된 코드에서, 각각의 foo 메서드는 어떤 메서드를 오버라이딩해서 사용할까?

 

1. Collect viable functions

일단 이름이 같고 파라미터 수가 같은 메서드들을 후보로 설정한다. (파라미터의 타입은 고려하지 않고, 후보가 하나도 없으면 컴파일 에러가 발생한다.)

 

2. Exact match

후보 중 타입이 정확히 일치하는 메서드를 선택한다. (2 = int / 3.2 = double / 2.3f = float.. )

 

3. Promotion

정확히 일치하는 타입이 없으면 값 손실을 방지하기 위해 타입이 더 큰 요소를 따라간다. (int 4byte -> double 8byte)

 

4. Standard Type Conversion

 값 손실이 발생할 가능성이 있는 메서드라도 사용한다. (int 4byte -> char 1byte)

 

5. User-define 

사용자가 정의한 타입 변환을 시도하는데.. 잘 안 쓴다.

마지막 과정까지 왔는데도 후보가 하나도 없으면 컴파일 에러를 뱉는다.

 

 

 

후보가 여러 개일 경우 비용이 저렴한 쪽으로 선택한다.

 

이 때 타입 캐스팅의 비용은 공식 문서를 참고하는게 정확하지만, 기본적으로 변환하는 타입과의 차이가 클 수록 비용이 크다고 할 수 있다.

 

파라미터가 여러 개일 경우도 각각 비용을 따져서 계산한 후 판단하자.

반응형
저작자표시 (새창열림)

'Programming Language > C++' 카테고리의 다른 글

[C++] OOP (4) Type Casting  (0) 2022.10.19
[C++] OOP (3) Inheritance / 연산자 오버로딩  (1) 2022.10.12
[C++] C++과 Java  (1) 2022.10.05
[C++] Memory Allocation / static  (0) 2022.10.05
[C++] OOP (1) 개념 / namespace  (0) 2022.09.14

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • [C++] OOP (3) Inheritance / 연산자 오버로딩

    [C++] OOP (3) Inheritance / 연산자 오버로딩

    2022.10.12
  • [C++] C++과 Java

    [C++] C++과 Java

    2022.10.05
  • [C++] Memory Allocation / static

    [C++] Memory Allocation / static

    2022.10.05
  • [C++] OOP (1) 개념 / namespace

    [C++] OOP (1) 개념 / namespace

    2022.09.14
다른 글 더 둘러보기

정보

시간의화살 블로그의 첫 페이지로 이동

시간의화살

  • 시간의화살의 첫 페이지로 이동

검색

방문자

  • 전체 방문자
  • 오늘
  • 어제

카테고리

  • 분류 전체보기 (606) N
    • Algorithm (205)
      • Data Structure (5)
      • Theory && Tip (33)
      • Baekjoon (166)
      • ALGOSPOT (1)
    • Spring (123)
      • Spring (28)
      • Spring Web MVC (20)
      • Spring Database (14)
      • Spring Boot (6)
      • Spring 3.1 (11)
      • Spring Batch (6)
      • Spring Security (16)
      • JPA (12)
      • Spring Data JPA (5)
      • QueryDSL (4)
      • eGovFramework (1)
    • Programming Language (74)
      • Java (19)
      • JavaScript (15)
      • C (25)
      • C++ (12)
      • Python (1)
      • PHP (2)
    • Computer Science (69) N
      • Operating System (18)
      • Computer Network (17) N
      • System Programming (22)
      • Universial Programming Lang.. (8)
      • Computer Architecture (4)
    • Database (21)
      • Database (7)
      • MySQL (3)
      • Oracle (3)
      • Redis (5)
      • Elasticsearch (3)
    • DevOps (20)
      • Docker && Kubernetes (8)
      • Jenkins (4)
      • Github Actions (0)
      • Amazon Web Service (8)
    • Machine Learning (28)
      • AI Introduction (28)
    • Mobile (28)
      • Android (21)
      • Flutter (7)
    • Solutions (13)
    • Life Logs (0)
    • 낙서장 (25)

최근 글

나의 외부 링크

메뉴

  • 홈

정보

13months의 시간의화살

시간의화살

13months

블로그 구독하기

  • 구독하기
  • RSS 피드

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. Copyright © 13months.

티스토리툴바