-
[C++] 클래스 기본 문법 2C++/이것이 C++이다 2021. 3. 12. 04:48
이전 글에서는 클래스의 구성 요소에 대해 간단하게 살펴보았습니다.
이번 글도 이전 글에서 이어지는 내용입니다.... 만
기본 문법이라기엔, 몰라도 클래스를 정의하는데 문제는 생기지 않습니다.
다만, 객체지향의 관점에서 살펴볼 때 이슈가 될 수 있는 부분이 되지 않을까 합니다.
1. 상수형 메소드 (Const method)
더보기상수형 메소드는 멤버 변수에 대해 읽기는 가능하지만 쓰기는 제한된 메소드를 의미합니다.
사용은 메소드 뒤에 'const' 예약어를 붙이는 것으로 사용할 수 있습니다.
Example (Const method)
#include <iostream> #include <stdio.h> class MyClass { public: int my_number; MyClass(int num) :my_number(num) { std::cout << "Construct : " << this->my_number << "\n"; } void function1() { my_number = my_number + 1; std::cout << "Function1, Number : " << this->my_number << "\n"; } void function2() const { my_number = my_number + 1; // Line 17 : Error std::cout << "Function2, Number : " << this->my_number << "\n"; } void function3() const { function1(); // Line 22 : Error } }; int main() { }
E0137 : 식이 수정할 수 있는 lvalue여야 합니다. E1086 : 개체에 멤버 함수 "MyClass::function1"과(와) 호환되지 않는 형식 한정자가 있습니다. C2662 : 'void MyClass::function1(void)':'this'포인터를 'const MyClass'에서 'MyClass &'(으)로 변환할 수 없습니다. C3490 : 'my_number'은(는) const 개체를 통해 액세스되고 있으므로 수정할 수 없습니다.
예제 코드는 17라인과 22라인의 소스코드때문에 컴파일이 되지 않는 것을 볼 수 있습니다.
상수형 메소드는 단순히 멤버 함수의 쓰기만 제한되어 있을 뿐만 아니라 멤버 함수의 호출 또한 불가능합니다.
또한, 에러 메시지의 C2662를 보면 메소드를 상수화 하는 방법에 대해 유추해 볼 수 있는데
상수화는 this포인터를 상수형 포인터로 변경하는 것임을 알 수 있습니다.
상수화는 클래스 설계에 있어서 안전장치와도 같습니다.
지속적으로 개발을 하다보면 남의 코드를 수정해야 될 때는 물론이고, 오래 전 자신이 개발했던 코드를 수정해야 할 일이 생길 수 있습니다.
문서화가 잘 되어있으면 좋겠지만, 매번 그것이 보장되지는 않을 것 입니다.
수정에 있어 타인의, 혹은 과거의 자신의 의도를 벗어날 수 있고, 이것이 안정성에 큰 영향을 미치게 될 수 있는데 const와 같은 상수형 예약어는 이것에 대한 기초적인 대책이 될 수 있습니다.
항상 사용자와 미래에 대해 고민하는것을 잊지 않도록 합시다.
2. 상수형 메소드의 예외사항 (mutable, const_cast<>)
더보기상위 문단의 말미에 사용자와 미래에 대해 고민하는것을 잊지 말자고 했습니다.
또한, 상수화는 설계에 있어 기초적인 안전장치가 될 수 있다는 것을 생각 해 보았습니다.
하지만 과거의 설계에 오점이 존재하거나, 혹은 기타의 이유로 이 안전장치를 제거해야 할 수 있습니다.
그 때 필요한것이 mutable예약어와 const_cast<>연산자 입니다.
우선 mutable은 변수의 자료형 앞에 붙이는 예약어 입니다.
mutable 변수는 const선언 된 상수형 메소드에서도 쓰기가 허용됩니다.
Example
#include <iostream> #include <stdio.h> class MyClass { public: mutable int my_number; MyClass(int num) :my_number(num) { std::cout << "Construct : " << this->my_number << "\n"; } void function1() { my_number = my_number + 1; std::cout << "Function1, Number : " << this->my_number << "\n"; } void function2() const { my_number = my_number + 1; std::cout << "Function2, Number : " << this->my_number << "\n"; } }; int main() { MyClass a(10); a.function2(); }
Construct : 10 Function2, Number : 11
상위 문단의 예제 소스에서, 멤버 변수에 mutable을 선언해 준 것 외에는 변한 것이 없습니다.
이번에는 문제없이 정상적으로 실행되는 것을 볼 수 있습니다.
다음은 const_cast<>예약어 입니다.
이 예약어는 상수형 참조를 일반 참조로 형 변환하는 예약어입니다.
Example
#include <iostream> #include <stdio.h> class MyClass { public: int my_number; MyClass(int num) :my_number(num) { std::cout << "Construct : " << this->my_number << "\n"; } void change(const int &number) { int &newRef = const_cast<int &>(number); newRef = newRef + 1; } }; int main() { int num = 5; std::cout << num << "\n"; MyClass a(10); a.change(num); std::cout << num << "\n"; }
5 Construct : 10 6
원래대로면 쓰기가 불가능할 change메소드의 파라미터에 대한 쓰기가 된 것을 볼 수 있습니다.
사용은 const_cast<새 형식>(대상)과 같은 형태로 사용합니다.
위와 같이 const에 대한 회피책을 이용하는 것은 새로운 좋은 설계에 대해 기존의 소스를 덜 수정하는 방안이 될 수도 있지만, 그렇지 않은 경우에 대해서는 안정성을 깨뜨리는 방법이 될 수 있습니다.
사용에 신중을 기하며, 남용하지 않도록 주의하는 것이 좋겠습니다.
3. 메소드 오버로딩
더보기오버로딩에 대해서는 이전 글 에서 다루었으므로 설명은 생략하도록 하겠습니다.
메소드 또한 기존 함수처럼 오버로딩이 가능합니다.
오버로딩에 대한 예제는 중복되는 내용이 될테니 생략하도록 하고, 이번 문단에서는 새로운 것을 소개하고자 합니다.
바로 호출 측에서의 호출을 차단하는 방법입니다.
Example (Unintended method call)
#include <iostream> #include <stdio.h> class MyClass { public: int my_number; void set_data(int num) { my_number = num; std::cout << my_number << "\n"; } }; int main() { MyClass a, b; a.set_data(5); b.set_data(0.5); }
5 0
b의 파라미터는 int로 자동 형 변환 되어 0이 대입되게 됩니다.
만약 개발자가 실수형에 대해 호출을 막고싶을 경우, 다음과 같은 선언을 통해 해결할 수 있습니다.
Example (Delete method)
#include <iostream> #include <stdio.h> class MyClass { public: int my_number; void set_data(int num) { my_number = num; std::cout << my_number << "\n"; } void set_data(double num) = delete; }; int main() { MyClass a, b; a.set_data(5); b.set_data(0.5); }
C2280 : 'void MyClass::set_data(double)': 삭제된 함수를 참조하려고 합니다.
다음과 같이, double형의 파라미터에 대한 호출을 원천적으로 차단 할 수 있습니다.
어설프고 복잡한 예외처리보다는, 필요없는 함수에 대한 과감한 삭제가 도움이 될 수도 있습니다.
4. 정적 멤버 (Static value, method)
더보기클래스의 메소드를 사용하려면 해당 클래스의 인스턴스를 생성해야 합니다.
하지만 이런 방식이 불필요한 메소드, 혹은 변수를 낳을 수 있는데, 이럴 때 정적 메소드, 정적 변수가 이용됩니다.
정적 메소드, 정적 변수는 인스턴스 생성 없이 직접 호출될 수 있습니다.
Example (Static value, Static method)
#include <iostream> #include <stdio.h> class MyClass { public: int my_number; static int st_number; static void st_function() { std::cout << st_number << "\n"; } }; int MyClass::st_number = 10; int main() { MyClass::st_function(); }
10
정적 메소드, 정적 변수는 변수나 메소드 앞에 static 예약어를 붙이는 것으로 선언 할 수 있습니다.
정적 메소드, 변수에는 다음과 같은 특이사항이 존재합니다.
- 정적 메소드는 this 포인터를 사용 할 수 없다.
- 정적 변수는 반드시 선언과 정의를 분리해야 한다.
사실 정적 메소드와 정적 변수는 전역 변수, 전역 함수와 크게 다르지 않지만
전역 함수와 전역 변수를 남발하는 것은 객체지향의 의미를 흐리게 할 수 있습니다.
이번 글에서는
1. 이렇게 설계하면 위험한데 -> 이런 방법이 있어요 (const)
2. 이건 이렇게 하면 될거같은데 -> 그거보단 이게 나아요 (delete, static)
이런 내용을 다룬 것 같습니다.
읽어주셔서 감사합니다.
'C++ > 이것이 C++이다' 카테고리의 다른 글
[C++] 연산자 (0) 2021.07.16 [C++] 깊은 복사와 얕은 복사 (0) 2021.03.30 [C++] 클래스 기본 문법 1 (0) 2021.02.27 [C++] 클래스를 살펴보기 전에 (0) 2021.02.27 [C++] 네임스페이스 (0) 2021.02.12