본문 바로가기

카테고리 없음

생성자 사용 시 주의사항 in C++

C++에서 생성자를 사용할 때 주의해야 할 사항들은 다음과 같습니다.


기본 생성자(Default Constructor) 제공 여부 확인

  • 클래스에 생성자를 정의하지 않으면 컴파일러가 자동으로 기본 생성자를 제공하지만, 다른 생성자가 정의되어 있으면 기본 생성자는 자동으로 생성되지 않습니다.
  • 기본 생성자가 필요한 경우 명시적으로 정의해야 합니다.
class MyClass {
public:
    MyClass(int value) { } // 기본 생성자가 없음
};

MyClass obj; // 오류 발생! 기본 생성자가 없음

 

해결 방법

class MyClass {
public:
    MyClass() {}  // 기본 생성자 명시적 정의
    MyClass(int value) {}
};

멤버 변수 초기화 순서 확인

  • 멤버 변수는 선언된 순서대로 초기화됩니다.
  • 초기화 리스트에서 순서를 바꾸더라도 실제 초기화 순서는 선언된 순서에 따릅니다.
class MyClass {
    int x;
    int y;
public:
    MyClass(int a, int b) : y(b), x(a) {} // x가 y보다 먼저 선언되었지만, y가 먼저 초기화됨!
};

 

해결 방법

class MyClass {
    int x;
    int y;
public:
    MyClass(int a, int b) : x(a), y(b) {} // 선언된 순서대로 초기화
};

초기화 리스트 사용 권장

  • 생성자 내부에서 대입 연산(=)을 하는 것보다 초기화 리스트(initializer list) 를 사용하는 것이 성능적으로 유리합니다.
  • 특히 const 멤버나 참조 멤버는 반드시 초기화 리스트를 사용해야 합니다.
class MyClass {
    const int value;
public:
    MyClass(int val) : value(val) {} // 초기화 리스트 사용 (올바름)
};

동적 메모리 할당 시 자원 누수 방지

  • 생성자에서 new를 사용하여 메모리를 할당했다면, 소멸자에서 반드시 delete를 호출해야 합니다.
  • 예외 발생 시 자원 누수를 막기 위해 스마트 포인터(std::unique_ptr, std::shared_ptr) 사용을 권장합니다.
class MyClass {
    int* data;
public:
    MyClass() { data = new int[100]; }
    ~MyClass() { delete[] data; } // 소멸자에서 메모리 해제
};

 

스마트 포인터 사용 예시

#include <memory>

class MyClass {
    std::unique_ptr<int[]> data;
public:
    MyClass() : data(std::make_unique<int[]>(100)) {} // 자동 메모리 관리
};

복사 생성자와 대입 연산자 정의

  • 클래스에 동적 메모리 할당이 포함된 경우, 기본 제공 복사 생성자와 대입 연산자를 사용하면 얕은 복사(Shallow Copy) 문제가 발생할 수 있습니다.
  • 명시적으로 깊은 복사(Deep Copy) 를 수행하는 복사 생성자와 대입 연산자를 정의해야 합니다.
class MyClass {
    int* data;
public:
    MyClass(int val) { data = new int(val); }

    // 깊은 복사 생성자
    MyClass(const MyClass& other) {
        data = new int(*other.data);
    }

    // 깊은 복사 대입 연산자
    MyClass& operator=(const MyClass& other) {
        if (this == &other) return *this; // 자기 자신 대입 방지
        delete data;
        data = new int(*other.data);
        return *this;
    }

    ~MyClass() { delete data; }
};

이동 생성자와 이동 대입 연산자 고려 (C++11 이상)

  • 불필요한 복사를 줄이기 위해 이동 생성자(move constructor)이동 대입 연산자(move assignment operator) 를 정의하는 것이 성능 최적화에 유리합니다.
class MyClass {
    int* data;
public:
    MyClass(int val) : data(new int(val)) {}

    // 이동 생성자
    MyClass(MyClass&& other) noexcept : data(other.data) {
        other.data = nullptr; // 소유권 이전
    }

    // 이동 대입 연산자
    MyClass& operator=(MyClass&& other) noexcept {
        if (this == &other) return *this;
        delete data;
        data = other.data;
        other.data = nullptr;
        return *this;
    }

    ~MyClass() { delete data; }
};

명시적(explicit) 키워드 사용

  • 단일 매개변수를 받는 생성자는 암시적 변환이 발생할 수 있어 explicit 키워드를 추가하여 이를 방지해야 합니다.
class MyClass {
public:
    explicit MyClass(int value) {} // 암시적 변환 방지
};

void func(MyClass obj) {}

int main() {
    func(10); // 오류 발생! 명시적 변환 필요
    func(MyClass(10)); // 올바른 사용법
}

가상 함수가 있는 경우 가상 소멸자 선언

  • 기본 클래스의 소멸자가 가상 함수(virtual)가 아니면, 파생 클래스의 소멸자가 호출되지 않을 수 있음.
class Base {
public:
    virtual ~Base() {} // 가상 소멸자 선언 (필수)
};

class Derived : public Base {
    int* data;
public:
    Derived() { data = new int[100]; }
    ~Derived() { delete[] data; }
};

결론

C++에서 생성자를 사용할 때 주의해야 할 주요 사항들은 다음과 같습니다.

  1. 기본 생성자의 자동 생성 여부 확인
  2. 멤버 변수 초기화 순서 확인
  3. 초기화 리스트 사용
  4. 동적 메모리 할당 시 자원 관리
  5. 복사 생성자와 대입 연산자 정의
  6. 이동 생성자와 이동 대입 연산자 활용
  7. 암시적 변환을 방지하기 위한 explicit 사용
  8. 기본 클래스의 가상 소멸자 선언

이러한 사항을 잘 지키면 메모리 누수 방지, 성능 최적화, 예기치 않은 동작 방지 등의 이점을 얻을 수 있습니다. 🚀