C ++ 클래스 생성자에서 실패 사례를 처리하는 방법은 무엇입니까?


21

생성자가 일부 작업을 수행하는 CPP 클래스가 있습니다. 이러한 작업 중 일부가 실패 할 수 있습니다. 생성자가 아무것도 반환하지 않는다는 것을 알고 있습니다.

내 질문은

  1. 생성자에서 멤버를 초기화하는 것 외에 다른 작업을 수행 할 수 있습니까?

  2. 생성자에서 일부 작업이 실패했음을 호출 함수에 알릴 수 있습니까?

  3. new ClassName()생성자에서 오류가 발생하면 NULL 을 반환 할 수 있습니까 ?


22
생성자 내에서 예외를 throw 할 수 있습니다. 완전히 유효한 패턴입니다.
Andy

1
GoF생성 패턴 중 일부를 살펴보아야 할 것입니다 . 공장 패턴을 권장합니다.
SpaceTrucker

2
# 1의 일반적인 예는 데이터 유효성 검사입니다. IE 만약 당신이 Square하나의 매개 변수를 갖는 생성자와 측면의 길이를 가진 클래스를 가지고 있다면 , 당신은 그 값이 0보다 큰지 확인하고 싶습니다.
David는 Reinstate Monica

1
첫 번째 질문에서는 가상 함수가 생성자에서 직관적으로 작동 할 수 있음을 경고합니다. 해체 자와 동일합니다. 그런 전화를 조심하십시오.

1
# 3-왜 NULL을 반환하고 싶습니까? OO의 이점 중 하나는 반환 값을 확인할 필요가 없다는 것입니다. 적절한 잠재적 예외를 catch ()하십시오.
MrWonderful

답변:


42
  1. 예, 일부 코딩 표준은이를 금지 할 수 있습니다.

  2. 예. 권장되는 방법은 예외를 던지는 것입니다. 또는 개체 내에 오류 정보를 저장하고이 정보에 액세스하는 방법을 제공 할 수 있습니다.

  3. 아니.


4
생성자 인수의 일부가 요구 사항을 충족하지 않아서 오류로 표시되어 있어도 개체가 여전히 유효한 상태가 아닌 경우에는 2) 권장하지 않습니다. 객체가 유효한 상태에 있거나 전혀 존재하지 않는 것이 좋습니다.
Andy

@DavidPacker 합의, 여기를 참조하십시오 : stackoverflow.com/questions/77639/… 그러나 일부 코딩 지침에서는 예외가 금지되어있어 생성자에게 문제가됩니다.
Sebastian Redl

어쨌든 나는 이미 그 대답에 대한 찬성 투표를했다, 세바스찬 흥미 롭군 : D
Andy

10
@ooxi 아니요, 그렇지 않습니다. 재정의 된 새 함수는 메모리를 할당하기 위해 호출되지만 연산자가 반환 된 후 컴파일러가 생성자에 대한 호출을 수행하므로 오류가 발생하지 않습니다. 그것은 새로운 것이 전혀 없다고 가정합니다. 스택 할당 된 객체를위한 것이 아니며, 대부분 객체이어야합니다.
Sebastian Redl

1
# 1의 경우 RAII는 생성자에서 더 많은 작업이 필요할 수있는 일반적인 예입니다.
Eric

20

계산을 수행하고 성공한 경우 또는 실패한 경우가 아닌 객체를 반환하는 정적 메서드를 만들 수 있습니다.

이러한 객체 구성 방법에 따라 정적이 아닌 방법으로 객체를 구성 할 수있는 다른 객체를 만드는 것이 좋습니다.

간접적으로 생성자를 호출하는 것을 종종 "공장"이라고합니다.

또한 null 객체를 반환 할 수 있는데, 이는 null을 반환하는 것보다 더 나은 솔루션 일 수 있습니다.


@null 감사합니다! 불행히도 여기에 두 가지 답변을 받아 들일 수 없습니다 :( 그렇지 않으면이 답변도 수락했을 것입니다! 다시 한번 감사드립니다!
MayurK

@ MayurK는 걱정할 필요가 없습니다. 허용 된 답변은 정답을 표시 하는 것이 아니라 귀하에게 도움이되는 답변입니다.
null

3
@null : C ++에서는을 반환 할 수 없습니다 NULL. 예를 들어 int foo() { return NULL실제로 0정수 객체를 반환합니다 (0). 에서 std::string foo() { return NULL; }실수로 전화 것입니다 std::string::string((const char*)NULL)(NULL은 \ 0 끝나는 문자열을 가리 키지 않습니다) 정의되지 않은 동작이된다.
MSalters

3
std :: optional은 멀리 떨어져 있지만 항상 그렇게하려면 boost :: optional을 사용할 수 있습니다.
Sean Burton

1
@Vld : C ++에서 객체 는 클래스 유형으로 제한되지 않습니다. 그리고 일반적인 프로그래밍으로에 대한 팩토리로 끝나는 것은 드문 일이 아닙니다 int. 예 std::allocator<int>를 들어 완벽하게 제정 된 공장입니다.
MSalters

5

@SebastianRedl은 이미 간단하고 직접적인 답변을 제공했지만 추가 설명이 유용 할 수 있습니다.

TL; DR = 생성자를 간단하게 유지하는 스타일 규칙이 있으며 그 이유가 있지만 이러한 이유는 주로 역사적인 (또는 단순히 나쁜) 스타일의 코딩과 관련이 있습니다. 생성자에서 예외 처리는 잘 정의되어 있으며 완전히 구성된 로컬 변수 및 멤버에 대해 소멸자가 계속 호출되므로 관용적 C ++ 코드에는 아무런 문제가 없어야합니다. 스타일 규칙은 어쨌든 지속되지만 일반적으로 문제가되지는 않습니다. 모든 초기화가 생성자에 있어야하는 것은 아니며 특히 생성자에 반드시 필요한 것은 아닙니다.


생성자가 정의 된 유효한 상태를 설정하기 위해 가능한 최소를 수행 하는 것이 일반적인 스타일 규칙 입니다. 초기화가 더 복잡한 경우 생성자 외부에서 처리해야합니다. 생성자가 설정할 수있는 초기화하기 저렴한 값이 없으면 클래스에 의해 시행되는 불변 인을 약화시켜 값을 추가해야합니다. 예를 들어, 클래스를 관리하기 위해 스토리지를 할당하는 것이 너무 비싸다면, 할당되지 않은 아직 null 상태를 추가하십시오. 아헴.

일반적으로이 극단적 인 형태에서는 절대적인 것과는 거리가 멀다. 특히 내 풍자에서 알 수 있듯이, 나는 불변의 약화는 거의 항상 가격이 너무 높다고 캠프에 있습니다. 그러나 스타일 규칙 뒤에는 이유가 있으며, 최소한의 생성자 강력한 불변을 둘 수있는 방법이 있습니다 .

그 이유는 특히 예외 상황에서 자동 소멸자 정리와 관련이 있습니다. 기본적으로 컴파일러가 소멸자 호출을 담당 할 때 명확하게 정의 된 지점이 있어야합니다. 여전히 생성자 호출을 수행하는 동안 오브젝트가 완전히 생성 될 필요는 없으므로 해당 오브젝트의 소멸자를 호출하는 것은 유효하지 않습니다. 따라서 객체를 파괴하는 책임은 생성자가 성공적으로 완료 될 때만 컴파일러로 전송됩니다. 이것은 RAII (Resource Allocation Is Initialization)로 알려져 있으며 실제로 가장 좋은 이름은 아닙니다.

생성자 내에서 예외 처리가 발생하면 일반적으로 try .. catch.

그러나 이미 성공적으로 생성 된 객체의 구성 요소 는 이미 컴파일러 책임입니다. 즉, 실제로는 큰 문제가 아닙니다. 예 :

classname (args) : base1 (args), member2 (args), member3 (args)
{
}

이 생성자의 본문이 비어 있습니다. 의 생성자가base1 , member2그리고 member3예외 안전 걱정할 필요가 없습니다. 예를 들어 member2throw 생성자가 생성하면 해당 생성자가 자체 정리를 담당합니다. 기지 base1는 이미 완전히 건설되었으므로 소멸자가 자동으로 호출됩니다. member3부분적으로 구성되지 않았으므로 정리할 필요가 없습니다.

본문이 있더라도 예외가 발생하기 전에 완전히 작성된 로컬 변수는 다른 함수와 마찬가지로 자동으로 소멸됩니다. 원시 포인터를 저글링하거나 어떤 종류의 암시 적 상태 (다른 곳에 저장 됨)를 "소유"하는 생성자 본문-일반적으로 시작 / 획득 함수 호출이 종료 / 릴리스 호출과 일치해야 함을 의미합니다. 예외 안전 문제가 발생할 수 있지만 실제 문제가 있습니다. 클래스를 통해 자원을 올바르게 관리하지 못합니다. 예를 들어 unique_ptr생성자에서 raw 포인터를 로 바꾸면 unique_ptr필요한 경우 소멸자 가 자동으로 호출됩니다.

사람들이 최소 제작자를 선호하는 이유는 여전히 남아 있습니다. 하나는 단순히 스타일 규칙이 존재하기 때문에 많은 사람들이 생성자 호출이 저렴하다고 가정합니다. 이를 유지하면서도 여전히 불변성을 가지고있는 한 가지 방법은 불변성이 약화 된 별도의 팩토리 / 빌더 클래스를 사용하는 것인데, 이는 잠재적으로 많은 수의 정규 멤버 함수 호출을 사용하여 필요한 초기 값을 설정합니다. 초기 상태가 필요하면 해당 객체를 인수가 아닌 클래스의 생성자에 인수로 전달하십시오. 그것은 약한 불변 개체의 "장을 훔칠"수 있습니다 noexcept.

물론 make_whatever ()함수 에서 함수를 래핑 할 수 있으므로 해당 함수를 호출 한 사람은 변형 된 클래스 클래스를 볼 필요가 없습니다.


"생성자가 여전히 생성자 호출에있는 동안 오브젝트는 반드시 완전하게 구성 될 필요는 없으므로 해당 오브젝트에 대한 소멸자를 호출하는 것은 유효하지 않습니다. 따라서 오브젝트를 파괴하는 책임은 컴파일러에게만 전달됩니다. 생성자가 성공적으로 완료되면 위임 생성자 관련 업데이트를 실제로 사용할 수 있습니다. 가장 파생 된 생성자가 완료되면 객체가 완전히 생성 되며 , 위임 생성자 내에서 예외가 발생하면 소멸자 호출됩니다.
벤 Voigt

따라서 "최소한의 dodo"생성자는 private 일 수 있으며 "make_whatever ()"함수는 private 생성자를 호출하는 다른 생성자가 될 수 있습니다.
벤 Voigt

이것은 내가 익숙한 RAII의 정의가 아닙니다. RAII에 대한 나의 이해는 의도적으로 객체의 생성자에서 자원을 획득하고 소멸자에서 해제하는 것입니다. 이러한 방식으로 스택에서 객체를 사용하여 캡슐화하는 리소스의 획득 및 릴리스를 자동으로 관리 할 수 ​​있습니다. 전형적인 예는 구성 할 때 뮤텍스를 획득하여 파괴시 해제하는 잠금입니다.
Eric

1
@Eric-그렇습니다. 절대적으로 표준 관행입니다. 일반적으로 RAII라고하는 표준 관행입니다. 정의를 확장하는 것은 나뿐만 아니라 Stroustrup이기도합니다. 예, RAII는 리소스 수명주기를 객체 수명주기에 연결하는 것입니다. 정신 모델은 소유권입니다.
Steve314

1
@Eric-이전 답글이 잘못 설명되어 삭제되었습니다. 어쨌든 개체 자체는 소유 할 수있는 리소스입니다. 모든 것은 함수 나 정적 / 글로벌 변수 까지 체인에 소유자 있어야 main합니다. 사용하여 할당 된 객체는 new, 하지 않는 당신이 그 책임을 할당 할 때까지 소유하고 있지만, 스마트 포인터가 참조하는 힙 할당 된 객체를 소유하고, 용기는 자신의 데이터 구조를 소유하고 있습니다. 소유자는 조기 삭제를 선택할 수 있으며 소유자 소멸자는 궁극적으로 책임이 있습니다.
Steve314
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.