[C++] explicit과 mutable

muchan
muchan
∙∙조회 5

https://modoocode.com/253

위 사이트에서 공부한 내용 정리입니다


예제 코드

ini
#include <iostream>

class MyString {
  char* string_content;  // 문자열 데이터를 가리키는 포인터
  int string_length;     // 문자열 길이

  int memory_capacity;

 public:
  // capacity 만큼 미리 할당함.
  MyString(int capacity);

  // 문자열로 부터 생성
  MyString(const char* str);

  // 복사 생성자
  MyString(const MyString& str);

  ~MyString();

  int length() const;
};

MyString::MyString(int capacity) {
  string_content = new char[capacity];
  string_length = 0;
  memory_capacity = capacity;
  std::cout << "Capacity : " << capacity << std::endl;
}

MyString::MyString(const char* str) {
  string_length = 0;
  while (str[string_length++]) {
  }

  string_content = new char[string_length];
  memory_capacity = string_length;

  for (int i = 0; i != string_length; i++) string_content[i] = str[i];
}
MyString::MyString(const MyString& str) {
  string_length = str.string_length;
  string_content = new char[string_length];

  for (int i = 0; i != string_length; i++)
    string_content[i] = str.string_content[i];
}
MyString::~MyString() { delete[] string_content; }
int MyString::length() const { return string_length; }

int main() { MyString s(3); }

String 클래스를 새로 만든 예제인데, 여기에 MyString(int capacity) 생성자를 추가한 것이다

arduino
void DoSomethingWithString(MyString s) {
  // Do something...
}

만약 이런 함수가 있을 때

scss
DoSomethingWithString(MyString("abc"))

DoSomethingWithString("abc")

이 코드들은 둘 다 컴파일이 된다

처음 MyString("abc")는 MyString형이어서 당연히 컴파일 되는데,
아래 그냥 "abc"는 자동으로 컴파일러가 아래 생성자로 MyString형을 생성해서 컴파일이 가능하다

rust
MyString(const char* str);

이런 경우를 암시적 변환이라고 한다

scss
DoSomethingWithString(3)

그러면 MyString도 아니고 문자열도 아닌 이 문장은 컴파일이 될까?

scss
DoSomethingWithString(MyString(3))

정답은 MyString이 int인자로 받는 생성자가 있기 때문에 가능하다(암시적 변환)

그치만 이 코드는 누가 봐도 의도하지 않은 동작이고, 이런 의도하지 않은 암시적 변환을 막는 것이

바로 explicit이다.

arduino
explicit MyString(int capacity);

MyString에서 int인자를 받는 생성자에 explicit 키워드를 붙인다면

DoSomethingWithString(3) 해당 부분에서 컴파일 오류가 발생한다

ini
MyString s = "abc";  // MyString s("abc")
MyString s = 5;      // MyString s(5)

explicit은 위처럼 복사 생성자의 형태로 호출되는 것도 막는다

만약 int인자를 받는 생성자에 explicit을 붙인다면

ini
MyString s(5);   // 허용
MyString s = 5;  // 컴파일 오류!

위처럼 동작할 것이다

mutable

mutable로 선언한 변수는 const함수에서도 이들 값을 바꿀 수 있다

예제

c
#include <iostream>

class A {
  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();
}

여기서 DoSomething에 data_ = x 부분에서 컴파일 오류가 발생한다

const 함수 내에서 변수의 값을 바꾸려고 했기 때문이다

arduino
mutable int data_;

하지만 data_를 mutable로 선언한다면 정상적으로 실행된다

mutable이 왜 필요할까?

그냥 const 함수 안쓰고 일반 함수로 쓴 다음 mutable을 안쓰면 되지 않나? 라고 생각할 수 있다

여기서 const 함수를 왜 쓰는지 의미를 살펴봐야한다

const 함수 의미: 이 함수는 객체의 내부 상태에 영향을 주지 않습니다
대표 예) 읽기 작업만 하는 함수

위 사이트에서는 데이터베이 서버를 예시로 들었다

arduino
class Server {
  // .... (생략) ....

  // 이 함수는 데이터베이스에서 user_id 에 해당하는 유저 정보를 읽어서 반환한다.
  User GetUserInfo(const int user_id) const {
    // 1. 데이터베이스에 user_id 를 검색
    Data user_data = Database.find(user_id);

    // 2. 리턴된 정보로 User 객체 생성
    return User(user_data);
  }
};

대충 이런 구조인데, 데이터 베이스는 요청한 데이터를 받아오는 작업이 오래 걸리기 때문에 캐시를 사용한다

그러면 아래와 같은 구조가 나오게 되는데

scss
class Server {
  // .... (생략) ....

  Cache cache; // 캐쉬!

  // 이 함수는 데이터베이스에서 user_id 에 해당하는 유저 정보를 읽어서 반환한다.
  User GetUserInfo(const int user_id) const {
    // 1. 캐쉬에서 user_id 를 검색
    Data user_data = cache.find(user_id);

    // 2. 하지만 캐쉬에 데이터가 없다면 데이터베이스에 요청
    if (!user_data) {
      user_data = Database.find(user_id);

      // 그 후 캐쉬에 user_data 등록
      cache.update(user_id, user_data); // <-- 불가능
    }

    // 3. 리턴된 정보로 User 객체 생성
    return User(user_data);
  }
};

여기서 cache는 GetUserInfo가 const함수이므로 값을 바꾸면 컴파일 에러가 발생한다

ini
cache.update(user_id, user_data);

이렇게 캐시 값을 바꿀 때 컴파일 에러가 발생한다

하지만 데이터 베이스에서 값을 읽어오는데 const를 빼는 것도 의미상 맞지 않다

데이터 베이스에서 값을 읽어오는건 내부 값이 변경 안되니까 당연히 const 함수로 선언해야한다

이럴 때 cache 변수를 mutable로 선언해서 컴파일 에러를 막는 것이다