.. Cover Letter

ㅇ 공부#언어/(C++)기초

(기초 C++) 6장. C++에서의 함수 중복과 static 멤버

BrainKimDu 2023. 2. 26. 23:07

주의 : 이 글은 C의 기초문법에 대해 상세하게 다루지 않습니다. (즉 C언어에서 배울 수 있는 기초내용은 생략합니다)

※ 명품 C++ Programming 의 책을 참고하여 개인적으로 정리한 글입니다. 
이 글의 목적은 해당 책의 내용을 인용하여 더 쉽게 이해하고자 정리하고, 더 쉬운 예제를 통해 이해하는 것을 목표로 하고 있습니다.
명품 C++ Programming의 예제문제와 실습문제가 정말 좋으므로, 깊게 공부하고 싶다면 책을 구매하는 것을 추천드립니다.
책의 저작권 등등 각종 권한은 출판사와 지은이/옮긴이에 있습니다.

- 출판사: (주)생능 출판사
- 지음: 황기태

명품 C++ Programming - YES24


함수중복 (overloading)

객체지향의 특징인 다형성을 실현하는 사례로 C와 다른 C++의 특징 중 하나이다. 다음의 조건을 만족해야 overloading이 가능하다.
- 중복된 함수들의 이름이 동일 해야한다.
- 중복된 함수들은 매개 변수 타입이나 매개 변수의 개수가 달라야 한다.
- 함수 중복에 리턴 타입은 고려되지 않는다.

그래서 

int sum(int a, int b) {
	return a + b;
}

int sum(int b) {
	return 1 + b;
}

다음처럼 함수 중복이 가능하다. 그러나 밑에 경우는 중복을 허용하지 않는다.

int sum(int a, int b) {
	return a + b;
}

int sum(int b) {
	return 1 + b;
}

string sum(int a, int b) {
	return to_string(a + b);
}

앞서 살펴본 것처럼  생성자를 중복하는 것도 overloading의 사례라고 볼 수 있다. 또한 앞에서 언급했던 것처럼 C++에서는 한가지 특징이 있는데, 다음처럼 매개변수를 초기화 시켜놓는 경우(디폴트 매개변수)를 볼 수 있다.

#include <iostream>
#include <string>

using namespace std;

int sum(int a , int b = 1) {
	return a + b;
}

int main(void) {
	cout << sum(3);
}

이 경우 출력값은 4가 된다. 그러나 다음의 경우 오류를 뱉는다, 디폴트 매개변수는 맨뒤에 위치해야한다.

추가적으로 디폴트 매개변수가 선언이 되어있는 상태에서 매개변수를 두개 넣어도된다. 

#include <iostream>
#include <string>

using namespace std;

int sum(int a , int b=1) {
	return a + b;
}

int main(void) {
	cout << sum(3, 2);
}

 

디폴트 매개변수의 활용은 다음과 같다. 위의 코드는 결국 다음과 같은 코드라고 볼 수 있다.

#include <iostream>
#include <string>

using namespace std;

int sum(int a, int b) {
	return a + b;
}

int sum(int a) {
	return a + 1;
}

int main(void) {
	cout << sum(3, 2);
}

이렇게 됨으로써  함수의 중복(overloading)을 가능한 적게 적을 수 있게된다. 

 

함수중복 (overloading)의 모호성(ambiguous)

다음의 경우 컴파일러는 모호하다라는 경고를 보내는데, 다음과 같은 경우이다.
- 형 변환으로 인한 모호성
- 참조 매개 변수로 인한 모호성
- 디폴트 매개 변수로 인한 모호성

형 변환으로 인한 모호성
예를 들면 이런 것이다. 

#include <iostream>
#include <string>

using namespace std;

float sum(float a) {
	return a + 1;
}

int main(void) {
	cout << sum(3);
}

이 상태에서는 int형이 더 큰 float형으로 변환되니 문제없이 컴파일이 가능하다. 이때의 경우를 자동 형 변환이라고 한다. 다음의 경우 모호하다는 오류를 뱉는다. 

너가 원하는게 뭔데? 이런 느낌이라 생각하면 된다.

참조 매개 변수로 인한 모호성
중복된 함수의 차이가 참조 매개 변수인 경우 모호성이 생길 가능성이 있다.

#include <iostream>
#include <string>

using namespace std;

int sum(int a, int b){
	return a + b;
}

int sum(int a, int &b) {
	return a + b;
}

int main(void) {
	cout << sum(1, 2);
}

다음의 경우는 문제가 없으나 다음의 경우에는 문제가 발생한다.

#include <iostream>
#include <string>

using namespace std;

int sum(int a, int b){
	return a + b;
}

int sum(int a, int &b) {
	return a + b;
}

int main(void) {
	int a = 1;
	int b = 2;
	cout << sum(a, b);
}

모호함이 발생한 상태

 

디폴트 매개 변수로 인한 모호성
다음의 코드를 보면서 이해해보면 편하다. 

#include <iostream>
#include <string>

using namespace std;

int sum(int a){
	return a + 1;
}

int sum(int a, int b = 1) {
	return a + b;
}

int main(void) {
	int a = 1;
	cout << sum(a);
}

보면 당연히 오류가 발생할 것이라 느껴지는 부분이다.

 

static 멤버 변수

변수와 함수에 생명 주기와 사용범위를 지정하는 방식 중 하나로 다음의 특징을 가진다.
- 생명 주기 : 프로그램이 시작할 때 생성되고 프로그램이 종료할 때 소멸
- 사용범위 : 변수나 함수가 선언된 범위 내에서 사용, 전역이냐 지역이냐로 구분

non-static 멤버는 객체가 생성될 때 생성되고, 객체와 생명주기를 같이한다. 그러나 static 멤버는 객체의 멤버이지만 객체가 생기기 전에 이미 생성되어 있고, 객체가 사라져도 소멸되지 않는다. 그리고 static 멤버는 객체들의 공통된 멤버로서 객체 사이에 공유된다.

그러면 static 멤버를 선언해보도록 하자. 

#include <iostream>
#include <string>

using namespace std;


class animal {
	string name;
public:
	static int water;
};


int main(void) {
	
}

간단하게 멤버변수 앞에 static을 붙이면 된다. 물론 접근 지정도 가능하다. 어떤 느낌이냐면 name은 animal 객체마다 고유하지만, water는 모든 객체들이 나눠 가진다고 보면된다. 

모든 객체들이 나눠가지기 때문에 static 변수는 클래스 바깥쪽에서 구현이 이뤄져야 한다. 다음처럼 구현을 할 수 있다.

#include <iostream>
#include <string>

using namespace std;


class animal {
	string name;
public:
	static int water;

	void drink_water() {
		water -= 1;
	}

	int show_water() {
		return water;
	}
};

int animal::water = 100;


int main(void) {
	animal dog;
	animal cat;

	dog.drink_water();
	cout << dog.show_water() << endl;

	cat.drink_water();
	cout << dog.show_water();

}

다음 코드의 동작을 생각해보자. static 멤버 변수의 물을 먹는 dog와 cat  물을 먹을 때마다 dog객체로 남은 물을 확인해보면

객체가 물을 공유하고 있는 것을 알 수 있다. static은 또한 포인터로도 접근이 가능하다. 또한 객체를 통해서 접근을 하지 않아도 클래스를 통해서 접근이 가능하다.

cout << animal::water;

다음처럼 클래스명::변수 로 접근이 가능하다. 그래서 객체를 만들지 않고 우선적으로 static 멤버변수의 값을 조정할 수 있다.

#include <iostream>
#include <string>

using namespace std;


class animal {
	string name;
public:
	static int water;

	void drink_water() {
		water -= 1;
	}

	int show_water() {
		return water;
	}
};

int animal::water = 0;

int main(void) {
	animal dog;
	animal cat;

	animal::water = 50 ;

	dog.drink_water();
	cout << dog.show_water() << endl;

	cat.drink_water();
	cout << dog.show_water() << endl;

	


}

다음처럼..

자바에서는 내 기억이 맞다면 static 사용을 지양하라고 배웠다. 그 이유는 자바에서는 100% 캡슐화가 가능하기 때문이다. 그러나 C++은 상황이 다르다. 그렇기 때문에 전역변수와 전역함수를 선언할바에 static 사용을 지향해야 한다는 말이다.

static 멤버 함수

static 멤버함수는 오직 static 멤버 함수만 접근할 수 있다. static을 함수로 사용하는 경우 클래스의 객체화 없이 함수를 호출해야할 때 사용합니다. 

 

 

 


참고 문헌
Scott Douglas Meyers(2015). Effective C++. Protec Media(프로텍 미디어)
Robert C. Martin(2021).UML 실전에서는 이것만 쓴다(UML for Java Programmers). 인사이트
황기태(2021). 명품 C++ Programming. (주)생능출판사