C++ 생성자 default, delete 키워드

Seongcheol Jeon·2024년 9월 28일
0

CPP

목록 보기
8/47
post-thumbnail

C++11부터 생성자에서 default, delete 키워드 선언이 가능해졌다. 생성자defaultdelete 키워드는 특별한 의미를 가지며, 각각 생성자메서드의 기본 구현을 지정하거나 사용을 금지하는 데 사용된다. 이를 통해 더 명확하고 제어된 객체 생성과 관리가 가능하다.

C++11 이전부터 클래스 내부의 생성자를 만들지 않더라도, 기본적으로 기본 생성자, 복사 생성자, 대입 복사 연산자, 소멸자 등을 만들어 주었다. 참고로 C++11 이후부터는 기본 생성 함수에 move 생성자대입 연산자도 추가되었다.

default 키워드

default 키워드는 컴파일러에게 해당 생성자(또는 다른 멤버 함수)기본 동작을 생성 하라고 지시하는 것이다.

보통 개발자가 사용자 정의 생성자를 작성했을 때, C++기본 생성자(매개변수 없는 생성자)를 자동으로 생성하지 않으므로, default 키워드를 사용하여 기본 생성자를 명시적으로 요청할 수 있다. 이렇게 default 키워드로 컴파일러에게 요청을 하면 컴파일러기본 생성자를 명시적으로 만들어준다.

그렇기때문에 비어 있는 생성자소멸자를 구체화할 필요가 없으며, 기본 생성자소멸자 등을 분명하게 표시할 수 있다.

기본적으로 컴파일러가 생성해준 복사 생성자대입 연산자는 객체 복사 시 얕은 복사를 수행한다. 물론 멤버 변수가 값 형식일 경우 또는 주소를 그대로 복사해도 될 경우 깊은 복사를 하지 않아도 된다. 이 경우는 컴파일러가 알아서 만들어주는 것들을 써도 되기에 굳이 복사 생성자대입 연산자를 작성하지 않아도 되는 경우이다.

하지만 이는 코드를 작성한 사람만이 한눈에 이해할 수 있다. 게다가 코드가 매우 길다면 작성자 마저도 헛갈릴 수 있다. 또한 코드를 보는 다른 사람의 입장에서 생각해보자.
외부인은 이 코드를 분석하지 않는 이상, 코드를 작성한 사람이 생성자를 실수로 구현하지 않은 건지... 아니면 의도적으로 구현하지 않은 건지 알아체기 어려울 수 있다. 즉, 의도가 명확하지 않다는 단점이 있다. 프로그래밍에선 뭐든 명확한 것이 좋기 때문에 확실하게 해주는 것이 모두에게 이롭다.

명확하게 명시적으로 표현하는 것은 좋은 코드 스타일이다.

class MyClass {
public:
	// 컴파일러가 기본 생성자를 생성하게 한다.
	MyClass() = default;
    
    // 복사 생성자도 기본 생성 사용
    MyClass(const MyClass&) = default;
};

위의 경우, 기본 생성자복사 생성자가 컴파일러에 의해 자동으로 생성된다.
이를 통해 명시적으로 기본 구현을 허용하면서도 코드의 가독성명확성을 높일 수 있다.

class Foo {
public:
	Foo(const Foo &other) = default;
    Foo& operator=(const Foo &other) = default;
};

위의 경우, default 키워드를 사용하여 컴파일러가 만들어준 기본 복사 생성자 및 기본 대입 연산자를 사용하겠다는 뜻을 개발자에게 명확히 전달해준다.
즉, 깊은 복사를 하지 않아도 되므로 컴파일러가 구현해주는 디폴트 생성자 및 연산자를 사용하겠다 라고 명시적으로 표시해주는 것이다. 따라서 해당 코드를 읽는 사람은 이 클래스가 얕은 복사를 해도 된다는 명확한 의도를 받을 수 있다.

기본 생성자소멸자도 마찬가지이다. 메모리 할당 또는 해제가 필요없거나 기본 생성자소멸자를 그대로 사용해도 된다면 default 키워드를 통해 명시적으로 디폴트 값을 사용하겠다 라고 표시해주는 것이 좋다.

delete 키워드

delete 키워드는 해당 생성자(또는 다른 멤버 함수)사용하지 못하도록 막는 데 사용된다. 즉, 컴파일러가 생성해주는 디폴트 값이 필요하지 않아 생성되지 않도록 하는 것이 delete 키워드이다.

예를 들어 다음의 클래스 객체를 복사가 불가능하도록 만들고 싶다고 가정해보자. delete 키워드를 모른다면 다음과 같이 private 영역으로 옮겨, 클래스 외부에서 사용하지 못하도록 일종의 트릭을 걸 수 있다.
하지만 이는 복사 생성자대입 연산자를 생성하지 않는 것이 아니라 생성 후 사용하지 못하도록 막아놓은 것이다. 깔끔한 해결법이라고 볼 수는 없다.

class Foo {
public:
	...

private:
	Foo(const Foo &other) {}
    Foo& operator=(const Foo &other) {}
};

이럴 경우 다음과 같이 delete 키워드를 사용하여 명시적으로 복사를 하지 않겠다는 의도를 줄 수 있다.

class Foo {
public:
	...
    
private:
	Foo(const Foo &other) = delete;
    Foo& operator=(const Foo &other) = delete;
};

delete 키워드가 선언된 복사 생성자를 호출하려고 하면 다음과 같은 에러 메시지가 나온다.
Foo 클래스의 복사 생성자가 참조될 수 없습니다. 삭제된 함수입니다. 이렇게 명확한 에러 메시지를 전달해준다.

다음의 예는 특정 상황에서 객체의 생성을 막거나 복사 생성자를 금지하고 싶을 때, 사용할 수 있다.

class MyClass {
public:
	// 기본 생성자 사용 금지
	MyClass() = delete;
    
    // 복사 생성자 사용 금지
    MyClass(const MyClass&) = delete;
    
    // 특정 생성자만 허용
    MyClass(int x) { };
};

위의 경우 기본 생성자복사 생성자삭제(delete)되어 사용이 불가능하다. 오직 int를 인자로 받는 생성자만 사용할 수 있다.
이를 통해 의도치 않은 객체 생성을 방지할 수 있다.

정리

주요 사용 사례

기본 생성자만 허용하고 싶지 않을 때

객체를 반드시 특정 파라미터로 생성하도록 강제하고 싶을 때 사용한다.

class MyClass {
public:
	// 기본 생성자를 삭제하여 파라미터 없는 생성 금지
	MyClass() = delete;
    
  	MyClass(int x) { }
};

복사 또는 이동 생성자를 금지하고 싶을 때

객체 복사 또는 이동을 막고자 할 때 사용한다.

class MyClass {
public:
	// 복사 생성자 삭제
	MyClass(const MyClass&) = delete; 
    
    // 복사 할당 연산자 삭제
    MyClass& operator=(const MyClass&) = delete;
};

기본 동작을 유지하고 싶을 때

특정 조건에서만 사용자 정의 함수가 필요할 때, 나머지는 기본 동작을 유지하고 싶은 경우에 사용한다.

class MyClass {
public:
	// 기본 생성자 유지
    MyClass() = default;
    
    // 추가 생성자 구현
    MyClass(int x) { }
};

이처럼 defaultdelete는 객체의 생성을 제어하거나 의도치 않은 동작을 방지하는 데 유용하다.

0개의 댓글