포스트

클래스(Class)와 암시적 멤버 메서드

C++에서 클래스는 객체 지향 프로그래밍의 핵심 요소로, 데이터와 이 데이터를 조작하는 함수를 하나의 모듈로 묶는 역할을 합니다.

C++에서 클래스는 변수(멤버 데이터)함수(멤버 함수) 를 포함하는 사용자 정의 타입입니다.

클래스의 기본 구조

클래스는 class 키워드를 사용하여 정의하며, 일반적으로 다음과 같은 형태를 가집니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class 클래스이름
{
private:
    // 멤버 변수 (필드)
    자료형 변수명;
    
    // 멤버 함수
    반환형 함수명(매개변수);

public:
    // 생성자
    클래스이름(매개변수);

    // 소멸자
    ~클래스이름();
};
  1. 멤버 변수 (Member Variables)
    • 클래스 내부에 선언된 변수들로, 객체의 상태를 나타냅니다.
    • 접근 제한자에 따라 외부 접근 가능 여부가 결정됩니다.
  2. 멤버 함수 (Member Functions):
    • 클래스 내부에 선언된 함수들로, 객체의 행동(동작) 을 정의합니다.
    • 접근 제한자에 따라 접근 가능 여부가 결정됩니다.
  3. 생성자 (Constructor)
    • 클래스의 객체가 생성될 때 호출되며, 객체를 초기화하는 데 사용됩니다.
    • 생성자의 이름은 클래스 이름과 동일하고 반환형이 없습니다.
  4. 소멸자 (Destructor)
    • 클래스의 객체가 소멸될 때 호출되며, 자원 해제 등의 정리 작업을 수행합니다.
    • 소멸자의 이름은 클래스 이름 앞에 물결표(~) 가 붙습니다.

접근 제한자

  • private: 클래스 내부에서만 접근 가능. 기본 접근 지정자.
  • protected: 클래스 자신 및 파생 클래스에서 접근 가능.
  • public: 클래스 외부에서도 접근 가능.

암시적 멤버 메서드

C++에서 클래스에는 여러 종류의 암시적(implicit) 멤버 메서드가 존재합니다.
이들은 사용자가 명시적으로 정의하지 않더라도 컴파일러가 자동으로 생성해주는 메서드입니다.

기본 생성자와 소멸자 (Default Constructor)

기본 생성자와 기본 소멸자는 매개변수가 없는 생성자와 소멸자입니다.

사용자가 명시적으로 생성자를 정의하지 않으면 컴파일러가 자동으로 생성하지만, 만약 사용자가 기본 생성자가 아닌 다른 생성자를 정의했다면, 컴파일러는 기본 생성자를 제공하지 않습니다.

주의할 점은 클래스의 상속 관계에서 파생 클래스가 생성자를 호출하면, initializer에서 부모 클래스의 기본 생성자를 호출한다는 점입니다.

즉, 부모 클래스에서 기본 생성자를 생성하지 않았다면, 이 초기화 단계에서 문제가 발생하므로 기본 생성자는 생성해두는 편이 좋습니다.

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass
{
public:
    MyClass()
    {
        // 기본 생성자
    }
    ~MyClass()
    {
	    // 기본 소멸자
    }
};

복사 생성자 (Copy Constructor)

객체의 모든 멤버를 복사하는 생성자로, 명시적으로 정의하지 않으면 컴파일러가 기본 복사 생성자를 제공합니다.

이 생성자는 static 멤버를 제외한 멤버들의 얕은 복사 를 수행합니다.

얕은 복사와 깊은 복사 글

복사 생성자가 호출되는 경우

  1. 객체가 다른 객체로 초기화될 때.
  2. 객체가 함수에 값으로 전달(call-by-value)될 때.
  3. 함수가 객체를 값으로 반환할 때.

call-by-value 글

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
class MyClass
{
public:
    MyClass(){}
    // 복사 생성자
    MyClass(const MyClass& source)
	{
		cout << "복사 생성자 호출" << endl;
	}

};
void printMyClassValue(MyClass obj)
{
	cout << "printMyClassValue 함수 호출" << endl;
}

// 함수가 객체를 값으로 반환하는 경우
MyClass createMyClass()
{
    MyClass temp;
    return temp; // 복사 생성자가 호출됨
}

int main()
{
    // 객체를 다른 객체로 초기화하는 경우
    MyClass obj1;
    MyClass obj2 = obj1; // 복사 생성자 호출

    // 객체를 함수에 값으로 전달하는 경우
    printMyClassValue(obj1); // 복사 생성자 호출

    // 함수가 객체를 값으로 반환하는 경우
    MyClass obj3 = createMyClass(); // 복사 생성자 호출
}

이동 생성자 (Move Constructor)

이동 생성자는 R-value 참조(R-value reference)를 사용하여 다른 객체의 자원을 ‘이동’할 때 호출됩니다.

R-value 참조와 이동 글

이동 생성자는 객체 복사 대신 자원을 효율적으로 이전합니다.

C++11 이후에 도입된 기능으로, 클래스에 명시적인 이동 생성자가 없고, 복사 생성자가 있거나 이동 생성자가 필요할 때 컴파일러가 자동으로 생성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyClass
{
public:
    int* data;
    
    // 이동 생성자
    MyClass(MyClass &&other) : data(other.data)
    {
        other.data = nullptr; // 원본 객체의 포인터를 무효화
    }
    
    ~MyClass()
    {
        delete[] data;
    }
};

int main()
{
    MyClass obj1(10);
    MyClass obj2 = move(obj1); // 이동 생성자 호출
}

대입 연산자 (Copy Assignment Operator)

대입 연산자는 이미 생성된 객체에 다른 객체를 대입할 때 호출됩니다.

이는 = 연산자를 오버로드하여 정의됩니다.

기본 대입 연산자는 멤버 변수들을 얕은 복사(shallow copy)합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass
{
public:
    MyClass& operator=(const MyClass &other)
    {
        if (this == &other)
        {
            return *this; // 자기 자신에 대한 대입 방지
        }
        // 여기에서 멤버 변수 복사
        return *this;
    }
};

이동 대입 연산자 (Move Assignment Operator)

이동 대입 연산자는 R-value 참조를 사용하여 다른 객체의 자원을 이동할 때 호출됩니다.

이는 ‘move semantics’의 일환으로, 객체 복사 대신 자원을 효율적으로 이전하여 성능을 최적화할 수 있습니다.

R-value 참조와 이동 글

C++11 이후에 도입된 기능으로, 클래스가 이동 생성자 또는 이동 대입 연산자를 명시적으로 정의하지 않은 경우, 컴파일러가 필요에 따라 이동 대입 연산자를 자동으로 생성합니다.

기본 이동 대입 연산자는 멤버 변수를 이동하며, 이동 후 원본 객체는 유효하지만 비어 있는 상태가 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass
{
public:
    MyClass& operator=(MyClass &&other) noexcept
    {
        if (this == &other)
        {
            return *this; // 자기 자신에 대한 대입 방지
        }
        // 기존 자원 해제
        // 여기에서 자원 이동
        return *this;
    }
};

멤버 초기화 리스트 (Member Initialization List)

멤버 초기화 리스트는 C++에서 클래스의 생성자를 사용하여 멤버 변수를 초기화하는 방식입니다.

멤버 초기화 리스트는 생성자의 구현부가 시작되기 전에 멤버 변수를 초기화할 수 있게 해줍니다.

특히, 상속 관계에서 멤버 초기화 리스트의 처음에는 부모 클래스의 기본 생성자를 호출하는 부분이 암시적으로 숨겨져 있습니다.

멤버 초기화 리스트의 형식

멤버 초기화 리스트는 생성자의 선언 이후 괄호가 닫힌 직후에 콜론(:)으로 시작되고, 초기화할 멤버와 그 값을 할당하는 부분으로 구성됩니다. 각 초기화 대상은 쉼표(,)로 구분됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyClass
{
public:
    int value;
    double rate;
    const int constant;
    int& reference;

    // 멤버 초기화 리스트를 사용한 생성자
    MyClass(int v, double r, const int c, int& ref)
    : value(v), rate(r), constant(c), reference(ref)
    {
        // 생성자 본문
    }
};

멤버 초기화 리스트의 장점

  1. 효율성: 멤버 변수가 자신의 타입에 맞는 생성자로 직접 초기화됩니다. 따라서 멤버가 객체 타입일 경우, 별도의 기본 생성자 호출 후 복사 생성자 호출 과정 없이 직접적인 초기화가 가능하여 성능이 개선됩니다.
  2. 필수적 사용 케이스: 참조 멤버(int&)와 상수 멤버(const int)는 선언과 동시에 초기화해야 하므로 생성자 내에서 할당할 수 없습니다. 멤버 초기화 리스트를 사용해야만 합니다.
  3. 명시성과 가독성: 멤버 변수의 초기화 순서와 방법이 명확해지므로 코드의 의도를 더 쉽게 파악할 수 있습니다.

초기화 순서

멤버 초기화 리스트에서 멤버 변수의 나열 순서와 상관없이 실제 초기화 순서는 클래스 내에서 멤버 변수가 선언된 순서에 따릅니다.

따라서 초기화 리스트의 순서를 멤버 변수 선언 순서와 일치시키는 것이 좋습니다.

1
2
3
4
5
6
7
8
9
10
11
class MyClass
{
public:
    int a;
    int b;
	// a와 b의 초기화는 선언된 순서대로 a, b 순으로 진행됩니다.
    MyClass(int b, int a)
    : b(b), a(a)
    {
    }
};

참고

얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)

Call By Value, Call By Address, Call By Reference 란?

[L_Value & R_Value] 1. 기본적인 L-Value와 R-Value 구분하기

[L_Value & R_Value] 2. 우측 값 참조와 이동(move semantics)

[L_Value & R_Value] 3. C++ 11 값 범주 (Lvalue, Xvalue, Prvalue)

이 기사는 저작권자의 CC BY-NC-ND 4.0 라이센스를 따릅니다.