Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
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
Tags
more
Archives
Today
Total
관리 메뉴

뭐라도 쓰겠지

25.03.21 / 클래스(Class)와 구조체(Struct)의 생성자(Constructor)와 소멸자(Destructor) 본문

프로그래밍/C#

25.03.21 / 클래스(Class)와 구조체(Struct)의 생성자(Constructor)와 소멸자(Destructor)

김데피 2025. 3. 21. 15:41

클래스를 이야기하기 전에, 먼저 객체지향 프로그래밍(Object Oriented Programming)을 설명해야 한다. 객체 지향 프로그래밍은 OOP라고도 하는데, 코드 내 모든 것을 객체(Object)로 표현하려는 프로그래밍 패러다임을 뜻한다. 여기에서 객체는 세상의 모든 것을 지칭하는 단어이다. 사람도 객체이고 연필도 객체이고 자동차, 파일, 모니터, 상품 주문 등도 모두 객체이다.

 

이 세상에서 객체라고 할 만한 모든 것들이 갖고 있는 두 가지가 있다. 바로 속성과 기능이다. 사람의 속성과 기능을 뽑아보면, 속성으로는 피부색, 키, 몸무게 등을 뽑을 수 있고 기능으로는 걷기, 뛰기, 보기, 듣기 등을 뽑을 수 있다. 한 가지 더 예를 들어 컴퓨터 파일의 속성과 기능을 뽑아보겠다. 속성으로는 크기, 종류, 파일 생성 날짜 등이 있겠고, 기능으로는 수정, 삭제 등이 있겠다. 객체지향 프로그래밍을 시작하기 위해선 객체가 가진 속성과 기능을 골라낼 수 있는 눈이 필요하다.

 

속성과 기능을 어떻게 C# 코드로 표현할 수 있을까? 속성은 데이터로, 기능은 메소드로 표현하면 된다. 정리하자면 객체는 데이터와 메소드로 이루어진다.

 

객체지향 프로그래밍을 소개했으니 이제 클래스를 설명하겠다. 클래스는 객체를 만들기 위한 청사진이라 볼 수 있다. 클래스가 자동차 설계도라면, 객체는 생산된 실제 자동차라고 할 수 있다. 설계도는 자동차가 어떤 속성과 어떤 기능을 가져야 하는지를 지정하고, 속성 중에 변경가능한 것과 변경불가능한 것을 결정한다. 설계도는 실체를 가지지 않지만, 공장에서 제작된 자동차는 실체가 있어서 도로나 주차 공간을 차지한다. 자동차는 차대 번호는 물론, 다양한 색상과 휠 사이즈를 가질 수 있다. 설계도에서 지정한 변경가능한 속성들이기 때문이다.

 

마찬가지로, 클래스는 객체가 가지게 될 속성과 기능을 정의하지만 실체를 가지지 않는다. 클래스를 이용해 만든 객체가 실체를 가진다. 동일 클래스로 객체 3개를 만들면, 이 세 객체는 서로가 구분되는 고유한 실체를 가지며 저마다 메모리 공간(Heap 공간)을 차지한다.

 

예시를 보며 클래스에 대해 생각해보자.

public class CVector
{
    public double X;
    public double Y;

    // 기본 생성자
    public CVector()
    {
        X = 0;
        Y = 0;
    }

    // 파라미터 생성자
    public CVector(double x, double y)
    {
        X = x;
        Y = y;
    }

    // 소멸자(Destructor)
    ~CVector()
    {
        // 여기서 unmanaged 리소스 해제 등 처리를 할 수 있음
        // C#에서는 실제로 잘 쓰지 않는 패턴
    }
    
    // 멤버 메서드 예시
    public double GetLength()
    {
        return Math.Sqrt(X*X + Y*Y);
    }
}

클래스 CVector를 선언했다. CVector는 클래스이므로 참조 타입이고 힙 메모리에 생성된다. 속성으로 X와 Y를 가지고 기능으로 GetLength를 가진다.

 

실제 사용 예시를 보자.

var vec1 = new CVector();        // (0,0)으로 초기화
var vec2 = new CVector(3, 4);    // (3,4)로 초기화
double length = vec2.GetLength(); // 5.0

이런 식으로 사용할 수 있다.

 

그런데 코드 중간에 생성자와 소멸자라는 게 있는데, 이건 무엇일까?

 

생성자(Constructor)는 클래스를 new로 인스턴스화하거나, 구조체를 생성할 때 호출되는 특별한 메소드이다. 클래스나 구조체와 이름이 동일하고 반환형을 명시하지 않는다. 주된 기능으론 인스턴스가 메모리에 올라갈 때 멤버 변수나 프로퍼티(Properties)를 초기화하고, 필요하다면 다른 객체를 생성하거나 리소스를 확보하여 객체가 사용 가능한 상태가 되도록 준비하는 역할을 수행한다.

public CVector()
{
    X = 0;
    Y = 0;
}

기본 생성자(Default Constructor)의 형식이다. 인자가 없는 형태의 생성자이고, 아무런 매개변수를 잔달하지 않으며 new CVector()처럼 객체를 만들 때 자동으로 호출된다. 명시적으로 정의하지 않으면 C# 컴파일러가 클래스에 한해 디폴트 생성자를 암묵적으로 제공하지만, 구조체의 경우 C# 10 버전 이하 기준 사용자가 직접 파라미터 없는 생성자를 정의할 순 없다.

 

public CVector(double x, double y)
{
    X = x;
    Y = y;
}

파라미터 생성자(Parameter Constructor)의 형식이다. 필요한 값을 매개변수로 받아 멤버 변수에 대입함으로써 객체를 특정 상태로 초기화한다. 같은 클래스(또는 구조체) 내에서 생성자 오버로딩(Overloading)을 통해 여러 개의 생성자를 정의할 수 있다.

 

public struct SPoint
{
    public int X;
    public int Y;

    // 파라미터 있는 생성자
    public SPoint(int x, int y)
    {
        X = x;
        Y = y;
    }
}

구조체(Struct)에선 C# 10 버전 이하 기준으로 파라미터가 없는 생성자를 직접 정의할 수 없다. 파라미터가 있는 생성자는 가능하지만, 모든 필드를 반드시 초기화해야 할 필요가 있다.

 

위 예시에서 new SPoint(10, 20)처럼 생성하면, X는 10, Y는 20으로 초기화된 구조체 인스턴스가 만들어진다.

 

구조체도 new SPoint()처럼 사용하면 필드가 전부 기본값(0, false, NULL 등)으로 초기화된 상태가 되는데, 이는 언어가 제공하는 기본 동작이지, 직접 작성한 생성자가 호출되는 게 아니다.

 

생성자를 알아봤으니 소멸자도 알아보도록 하자.

~CVector() {
    Console.WriteLine("Destructor Call!");
}

소멸자(Destructor)는 C#에서 ~클래스이름() 형태로 작성하는 가비지 컬렉터가 더 이상 객체가 필요 없다고 판단해 메모리를 해제할 때 호출되는 특별한 메소드이다. C++의 소멸자와는 다르게, C#에서는 개발자가 임의로 호출할 수도 없고, 호출 시점도 예측하기 어렵다.

 

관리되지 않는 리소스(예를 들어 파일 핸들, 네이티브 소켓, 데이터베이스 연결 등)를 해제하는 데 사용한다. 단순한 메모리 해제 목적은 가비지 컬렉터가 처리하므로, 굳이 소멸자를 구현할 필요는 없다. 가비지 컬렉션과 직접적으로 관련된 특수한 동작이 필요하거나 외부 네이티브 리소스 정리가 필요한 경우에만 소멸자를 사용한다.

 

주의해야 할 점은 가비지 컬렉션 시점은 CLR(런타임)이 스케줄링하므로, 소멸자가 언제 실행될지 확신하기 어렵다. 즉, 동기적/순차적으로 코드를 실행하기에는 부적합하다. 또한 구조체에는 소멸자를 정의할 수 없다. 구조체는 스택 영역에 저장되며, 가비지 컬렉터가 작동하는 영역이 아니기 때문이다. 

 

클래스와 구조체의 차이점을 알아보며 글을 마무리하자.

 

C#에서 클래스는 참조 타입, 구조체는 값 타입이다. 크게 아래와 같은 차이가 있다.

 

  • 1. 메모리 할당과 동작
    • 클래스(참조 타입)
      • new 키워드를 사용하면 객체가 힙(Heap) 메모리에 동적 할당되고, 해당 객체의 '참조 주소'가 리턴된다.
      • 변수에는 힙에 생선된 객체의 참조(포인터 같은 개념)만 저장된다.
      • 다른 메소드나 변수에 넘길 때 같은 참조가 전달되므로, 한 인스턴스를 여러 군데에서 공유할 수 있다(객체 내부 상태가 공유됨).
    • 구조체(값 타입)
      • 구조체 변수는 스택(Stack)에 실제 데이터 값을 직접 담는다.
      • 메소드나 다른 변수에 전달할 때 값 자체가 복사되므로, 원본과 복사본이 서로 다른 인스턴스가 된다.
      • 크기를 큰 구조체를 복사하면 비용이 커질 수 있으므로, 통상적으로 가벼운(필드 수가 적고 크기가 작은) 데이터에 적합하다.
  • 2. 상속(Inheritance)와 다형성(Polymorphism)
    • 클래스
      • 다른 클래스로부터 상속받을 수 있고, 다른 클래스를 상속할 수도 있다.
      • 추상화, 다형성, 가상 메소드, 오버라이드 등 객체지향 기능을 풀로 활용할 수 있다.
    • 구조체
      • 클래스나 다른 구조체를 상속할 수 없다.
      • 다만 인터페이스(Interface)는 구현할 수 있다.
      • 값 타입 특성상, 상속을 통한 다형적 설계는 불가능하다.
  • 3. 생성자/소멸자 활용
    • 클래스
      • 매개변수 생성자, 기본 생성자 등 다양한 형태의 생성자를 자유롭게 정의할 수 있다.
      • 소멸자(~ClassName())를 정의할 수 있지만, 일반적으로 잘 사용하지 않는다.
    • 구조체
      • C# 10 버전 이하 기준 직접 기본 생성자를 작성할 수 없다. 컴파일러가 자동으로 제공하는 디폴트가 있다.
      • 파라미터가 있는 생성자는 정의할 수 있지만, 모든 필드를 명시적으로 초기화해야 한다.
      • 소멸자를 가질 수 없다.
  • 4. 활용 목적
    • 클래스
      • 큰 데이터와 복잡한 로직, 상속 및 다형성이 필요한 경우, 여러 개의 인스턴스를 생성해서 공유하는 경우에 적합하다.
      • 객체 간 참조 공유가 필요하거나, 생성/해제 시점과 동작을 명확히 제어해야 하는 경우 사용한다.
    • 구조체
      • 수 개의 필드로 구성된 가벼운 데이터 구조를 표현할 때 사용한다(예를 들어 2D/3D 좌표, 색상, 복소수, 단순 파라미터 묶음 등)
      • 값 복사가 직관적이어야 하거나, 굳이 힙 할당/가비지 컬렉션 부담을 주고 싶지 않을 때 사용한다.
      • .NET 라이브러리의 System.Drawing.Point, System.TimeSpan, System.Guid 등도 구조체로 되어있다.

'프로그래밍 > C#' 카테고리의 다른 글

25.03.24 / 델리게이트(Delegate)  (0) 2025.03.24
25.03.24 / C# 배열  (0) 2025.03.24
25.03.21 / 멤버 접근 지정자(Access Modifiers)  (0) 2025.03.21
25.03.20 / C# 데이터 타입  (0) 2025.03.20
25.03.20 / C# HelloWorld  (0) 2025.03.20