static
총 생성된 인스턴스의 수를 구하는 클래스가 있다고 하면? 어떻게 구현할 것인가?
대부분이 전역변수를 설정하여 구한다고 할 것이다.
전역변수의 경우 프로젝트의 크기가 커질수록 실수로 인해 겹쳐서 오류가 날 수 있어 사용하지 않는 편이 좋다.
따라서 전역변수 설정을 피하기 위해서 나온 클래스에만 종속되는 변수가 'static' 이다.
이 static 멤버 변수의 경우, 클래스의 모든 객체들이 '공유' 하는 변수로써 각 객체 별로 따로 존재하는 멤버 변수들과는 달리 모든 객체들이 '하나의' static 멤버 변수를 사용하게 된다.
static 멤버 변수의 경우, 멤버 변수들 처럼, 객체가 소멸될 때 소멸되는 것이 아닌, 프로그램이 종료될 때 소멸된다. static 멤버 변수는 아래와 같이 정의한다.
class Marine {
static int total_marine_num;
...
}
int Marine::total_marine_num = 0;
그리고 인스턴수의 개수를 알고 싶다면 생성자, 소멸자에 다음을 추가해주면 된다.
Marine::Marine(int x, int y): coord_x(x), coord_y(y) { total_marine_num++; }
Marine::~Marine(){ total_marine_num--; }
static 멤버 함수는 아래와 같이 정의하며, static 멤버변수에만 접근 가능하다.
this 키워드 사용해야 호출할 수 있는 일반적인 멤버함수나 멤버변수에는 접근할 수 없다.
class Marine {
static void getMarineNum(){
std::cout << total_marine_num << '\n';
}
...
}
explicit
원래 아래와 같이 생성자가 정의되어 있다면?
class MyString {
MyString(int capacity);
...
}
void DoSomethingWithString(MyString s) { // Do something... }
int main() {
DoSomethingWithString(3);
}
main에서 위와 같이 DoSomethingWithString(3); 쓰더라도 아래와 같이 암시적 변환되어 컴파일된다.
int main() {
DoSomethingWithString(MyString(3));
}
하지만 아래와 같이 explicit 키워드를 사용한다면?
원하지 않는 암시적 변환을 할 수 없도록 컴파일러에게 명시할 수 있으며,
DoSomethingWithString(3)처럼 사용할 경우 에러가 발생한다.
class MyString {
explicit MyString(int capacity);
...
}
void DoSomethingWithString(MyString s) { // Do something... }
int main() {
DoSomethingWithString(3);
}
mutable
원래 const 함수 내부의 멤버변수의 값을 바꾸는 것은 불가능하다.
하지만, 멤버변수를 mutable로 선언하였다면? const 함수에서도 값을 바꿀 수 있다.
#include <iostream>
class A {
mutable int data_;
public:
A(int data) : data_(data) {}
void DoSomething(int x) const {
data_ = x; // 가능!
}
void PrintData() const { std::cout << "data: " << data_ << std::endl; }
};
int main() {
A a(10);
a.DoSomething(3);
a.PrintData();
}
default
- 클래스는 기본적으로 기본 생성자, 소멸자, 복사 생성자, 대입연산자, 이동 생성자, 이동 대입 연산자를 생성해준다.
- 하지만 기본 생성자는 복사 생성자 또는 이동 생성자가 정의되면 자동으로 만들어주지 않는다.
- 따라서 기본 생성자를 새로 정의해주어야 하는데 default 함수를 이용하며 간략하게 만들 수 있다.
- 하지만 default 키워드로 생성자를 만든다면? 얕은 복사만 가능하다.
- 얕은 복사 (shallow copy) : 실제 데이터가 아닌 주소를 복사
- 깊은 복사 (deep copy) : 실제 데이터를 복사 (새로운 메모리를 할당)
- 따라서 "깊은 복사를 하지 않아도 되므로 컴파일러가 구현해주는 디폴트 함수 및 연산자를 사용하겠다" 라고 명시적으로 표시해주는 것
class Widget{
public:
Widget() = default; // 생성자를 기본적으로 만들어 줍니다.
Widget(const Widget& rhs) = default; // 복사생성자도 기본적으로 만들어줘요
private:
int a;
};
int main() {
Widget a;
}
delete
- C++11 이전엔 private 안에 복사 생성자와 대입 연산자를 정의하여 외부에서 접근하지 못하게 하는 방법을 사용하였다.
- C++11 이후엔 delete 키워드를 통하여 특정 함수에 대한 정의를 금지할 수 있다.
- 이는 "컴파일러가 생성해주는 디폴트 값이 필요없어 생성되지 않겠다"는 의미이다.
- delete 키워드가 선언된 복사 생성자를 호출하려고 해보면 다음과 같은 에러 메세지를 준다.
- cannot be referenced -- it is a deleted function
class Widget{
public:
Widget() : a(nullptr) { }
Widget(int _x) : a(new int) { *a = _x; }
Widget(const Widget& rhs) = delete;
Widget(Widget&& rhs) = delete;
Widget& operator=(Widget&& rhs) {
a = rhs.a;
rhs.a = nullptr;
return *this;
}
~Widget() { delete a; }
private:
int* a;
};
int main() {
Widget A(1); // ok, 기본 생성자로 생성합니다.
Widget B(Widget(1)); //ok, 복사생략으로 기본 생성자로 생성합니다.
Widget C;
C = Widget(10); //ok, 이동 대입 연산자로 초기화 됩니다.
Widget D = std::move(Widget(20)); //error, 이동 생성자는 삭제되었습니다.
}
참조
- [씹어먹는 C++] 클래스의 explicit 과 mutable 키워드: https://modoocode.com/253
- [Tistory] 나만의 연습장: https://openmynotepad.tistory.com/26
- [Tistory] 가독성을 위해 default와 delete: https://woo-dev.tistory.com/100
'Study: Software(SW) > SW: Language' 카테고리의 다른 글
[C++] 객체지향 프로그래밍: special member function (feat. L-value, R-value, std::move) (0) | 2022.06.30 |
---|---|
[C++] 객체지향 프로그래밍: Effective Modern C++ 정리 (0) | 2022.06.27 |
[C++] C++ Json 라이브러리 성능비교(벤치마크): JsonCpp, Nlohmann/json... (0) | 2022.06.16 |
[C++] 병행 컴퓨팅: concurrency, thread... (0) | 2022.06.16 |
[C++] 초심자가 자주 하는 C++ 실수 (0) | 2022.06.15 |