💙ref.

  1. 모두의 코드 씹어먹는 C++ - <4 - 1. 이 세상은 객체로 이루어져 있다>

  2. 모두의 코드 씹어먹는 C++ - <4 - 2. 클래스의 세계로 오신 것을 환영합니다. (함수의 오버로딩, 생성자)>
  3. 모두의 코드 씹어먹는 C ++ - <4 - 6. 클래스의 explicit 과 mutable 키워드>



1. 객체 지향

1.1. 객체

  • 객체: 변수들과 참고 자료들로 이루어진 소프트웨어 덩어리
    • 객체에 포함된 변수 및 함수: 인스턴스 변수, 인스턴스 메소드
  • 추상화: 현실 세계의 개념을 컴퓨터가 처리할 수 있도록 적절한 형태로 변환
  • 캡슐화: 외부에서 변수 값 변경 불가, 메소드를 통해 간접 접근
    • 객체 내부 동작 원리를 몰라도 해당 기능 사용 가능


1.2. 클래스

  • 객체의 설계도!
  • 어떠한 기능에 대한 틀(내부 구현 x)
    • 클래스를 이용해서 만들어진 객체를 인스턴스라고 부름
  • 클래스 구성 요소: 멤버 변수, 멤버 함수
  • private로 정의된 요소들은 객체 외부에서 접근 불가능
    • 일반적으로 멤버 변수는 private, 멤버 함수는 public으로 정의(캡슐화)



2. 클래스 다루기

2.1. 함수 오버로딩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

void print(int x) { std::cout << "int : " << x << std::endl; }
void print(char x) { std::cout << "char : " << x << std::endl; }
void print(double x) { std::cout << "double : " << x << std::endl; }

int main()
{
  int a = 1;
  char b = 'c';
  double c = 3.2f;

  print(a);
  print(b);
  print(c);

  return 0;
}

// 실행 결과
// int : 1
// char : c
// double : 3.2
  • 함수명이 같더라도 인자가 다르면 다른 함수로 판단

    • 컴파일러가 적합한 인자를 가지는 함수를 찾아 호출

      • 정확히 일치하는 타입이 없을 경우 형변환 수행

        대상 변환 결과
        char, unsigned char, short int
        unsigned short int,unsigned int
        float double
        enum int
      • 형변환을 수행해도 일치하는 타입의 함수가 없을 경우

        대상 변환 결과
        임의의 숫자(numeric) 다른 숫자 타입
        enum 임의의 숫자
        0 포인터, 숫자
        포인터 void 포인터
      • 유사한 타입의 함수를 찾을 수 없을 경우

        • 모호하다(ambiguous)고 판단해 오류 발생

          eg) error C2668: 'print' : ambiguous call to overloaded function


2.2. 생성자(constructor)

  • 객체 생성 시 자동으로 호출되는 함수
    • 클래스의 멤버 변수 초기화
    • 클래스 사용에 필요한 설정값 정의
  • 생성자 정의: /* 클래스 이름 */ (/* 인자 */) {}
  • 생성자 유형
    • 암시적 방법(implicit): Date day(2011, 3, 1);
    • 명시적 방법(explicit): Date day = Date(2012, 3, 1);


2.2.1 디폴트 생성자 (Default Constructor)

  • 생성자를 명시적으로 정의하지 않았을 경우 컴파일러가 자동으로 추가해주는 생성자
    • 개발자가 생성자를 정의하지 않아도 생성자가 호출됨!
    • 단, 컴파일러가 생성했으므로 아무런 동작도 하지 않음
  • 디폴트 생성자 사용자 정의 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

class Date {
  int year_;
  int month_;  // 1 부터 12 까지.
  int day_;    // 1 부터 31 까지.

 public:
  void ShowDate();

Date()		// 디폴트 생성자 정의
  {
    year_ = 2012;
    month_ = 7;
    day_ = 12;
  }
};


C++ 11부터 디폴트 생성자를 명시적으로 정의할 수 있다!

1
2
3
4
5
class Test 
{
 public:
  Test() = default;  // 디폴트 생성자 정의 명령어
};
  • 생성자 선언 뒤에 = default를 붙여 디폴트 생성자 명시


2.2.2 생성자 오버로딩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>

class Date {
  int year_;
  int month_;  // 1 부터 12 까지.
  int day_;    // 1 부터 31 까지.

 public:
  void ShowDate();

  Date() 
  {
    std::cout << "기본 생성자 호출!" << std::endl;
    year_ = 2012;
    month_ = 7;
    day_ = 12;
  }

  Date(int year, int month, int day) 
  {
    std::cout << "인자 3 개인 생성자 호출!" << std::endl;
    year_ = year;
    month_ = month;
    day_ = day;
  }
};
  • 인자를 다르게 한 여러 개의 생성자를 정의해 오버로딩 구현 가능



3. explicit / mutable

3.1. explicit

  • 원하지 않는 암시적 변환을 할 수 없도록 컴파일러에게 명시


예제1 - 암시적 변환 방지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class MyString 
{
  char* string_content;  // 문자열 데이터를 가리키는 포인터
  int string_length;     // 문자열 길이

  int memory_capacity;

 public:
  // capacity 만큼 미리 할당함. (explicit 키워드에 주목)
  explicit MyString(int capacity);

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

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

  ~MyString();

  int length() const;
  int capacity() const;
};

// .. (생략) ..

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

int main() 
{
  DoSomethingWithString(3);  // ????
}

// 컴파일 오류
// test5.cc:56:3: error: no matching function for call to 'DoSomethingWithString'
//   DoSomethingWithString(3); // ????
//   ^~~~~~~~~~~~~~~~~~~~~
// test5.cc:51:6: note: candidate function not viable: no known conversion from 'int' to 'MyString' for 1st argument
// void DoSomethingWithString(MyString s) {
//      ^
// 1 error generated.
  • int capacity를 받는 생성자가 explicit으로 명시
    • MyString 생성자를 explicit으로 선언해 해당 생성자를 이용한 암시적 변환 방지
      • int에서 MyString으로 변환 불가능


예제2 -복사 생성자 호출 방지

1
2
MyString s = "abc";  // MyString s("abc")
MyString s = 5;      // MyString s(5)
  • 컴파일러가 적절한 생성자를 호출
    • s에 값을 대입한다는 의미
1
2
MyString s(5);   // 허용
MyString s = 5;  // 컴파일 오류!
  • explicit으로 MyString(int capacity)를 설정할 경우 컴파일 오류 발생(복사 생성자 형태 호출 방지)


3.2. mutable

멤버 변수를 mutable로 선언할 경우 const함수의 멤버 변수를 변경할 수 있다.

  • 멤버 함수를 const로 선언하는 이유: 해당 함수는 객체 내부 상태에 영향을 주지 않는다는 의미 표현
    • eg) 읽기 작업을 수행하는 함수
  • 서버의 경우 메모리에 캐시를 만들어 자주 요청되는 데이터를 빠르게 조회
    • 즉각 캐시를 갱신하기 위해 const로 정의된 유저 서버 함수의 Cachemutable로 선언해 관리


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

class A 
{
  mutable int data_;	// mutable로 선언한 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();
}
  • mutable로 선언한 data_의 값이 const 함수 내부에서 변경됨
    • mutable로 선언하지 않을 경우 const 함수의 멤버 변수에 값 대입 불가능

Leave a comment