Nice programing

C ++에서 생성자의 실패를 처리하는 방법은 무엇입니까?

nicepro 2020. 11. 21. 09:16
반응형

C ++에서 생성자의 실패를 처리하는 방법은 무엇입니까?


클래스 생성자에서 파일을 열고 싶습니다. 개봉이 실패하여 개체 구성이 완료되지 않을 수 있습니다. 이 실패를 처리하는 방법? 예외를 버리시겠습니까? 이것이 가능하다면 던지지 않는 생성자에서 처리하는 방법은 무엇입니까?


객체 생성이 실패하면 예외를 발생시킵니다.

대안은 끔찍합니다. 구성이 성공하면 플래그를 만들고 모든 메서드에서 확인해야합니다.


클래스 생성자에서 파일을 열고 싶습니다. 개봉이 실패하여 개체 구성이 완료되지 않을 수 있습니다. 이 실패를 처리하는 방법? 예외를 버리시겠습니까?

예.

이것이 가능하다면 던지지 않는 생성자에서 처리하는 방법은 무엇입니까?

옵션은 다음과 같습니다.

  • 생성자가 던지지 않도록 앱을 다시 디자인하십시오. 가능하면 실제로 수행하십시오.
  • 성공적인 건설을 위해 플래그 및 테스트를 추가하십시오.
    • 생성자가 플래그를 테스트 한 직후에 합법적으로 호출 될 수있는 각 멤버 함수를 가질 수 있습니다. 이상적으로는 설정되어 있으면 던지지 만 그렇지 않으면 오류 코드를 반환합니다.
      • 이것은 추악하고 코드를 작업하는 개발자 그룹이 불안정한 경우 올바르게 유지하기가 어렵습니다.
      • 객체가 성공적으로 생성 된 버전과 항상 오류 버전의 두 가지 구현 중 하나를 다형 적으로 연기하도록하여이를 컴파일 할 때 확인할 수 있지만 힙 사용 및 성능 비용이 발생합니다.
    • 객체를 사용하기 전에 "is_valid ()"또는 유사한 함수를 호출해야한다는 요구 사항을 문서화하여 호출 된 코드에서 호출 수신자에게 플래그를 확인하는 부담을 이동할 수 있습니다. 다시 오류가 발생하기 쉽고 추악하지만 훨씬 더 분산되고 강제 할 수 없으며 통제 불능.
      • : 당신이 당신이 뭔가를 지원하는 경우보다 쉽고 발신자에 대한 지역화 조금 할 수 있습니다 if (X x) ...(일반적으로 제공함으로써, 즉 객체가, 부울 맥락에서 평가 될 수 operator bool() const또는 유사한 통합 변환),하지만 당신은 필요가 없습니다 x에 범위에 오류에 대한 자세한 내용을 쿼리하십시오. 이것은 예를 들어 친숙 할 수 있습니다.if (std::ifstream f(filename)) { ... } else ...;
  • 호출자가 열어야하는 스트림을 제공하도록합니다 ... (Dependency Injection 또는 DI라고 함) ... 어떤 경우에는 잘 작동하지 않습니다.
    • 생성자 내부에서 스트림을 사용할 때 여전히 오류가 발생할 수 있습니다.
    • 파일 자체는 호출자에게 노출되지 않고 클래스에 비공개 여야하는 구현 세부 사항 일 수 있습니다. 나중에 해당 요구 사항을 제거하려면 어떻게해야합니까? 예를 들어, 파일에서 미리 계산 된 결과의 조회 테이블을 읽고 있었지만 계산이 너무 빨라서 미리 계산할 필요가 없습니다. 모든 지점에서 파일을 제거하는 것이 고통 스럽습니다 (때로는 엔터프라이즈 환경에서는 비현실적 임). 잠재적으로 단순히 다시 연결하는 것보다 훨씬 더 많은 재 컴파일을 강제합니다.
  • 호출자가 생성자가 설정 한 성공 / 실패 / 오류 조건 변수에 버퍼를 제공하도록 강제합니다. 예 : bool worked; X x(&worked); if (worked) ...
    • 이 부담 상세 주목을 그리고 희망 의 필요성을 훨씬 더 의식 호출자가 개체를 건설 한 후 변수를 참조 할 수 있습니다
  • 호출자가 반환 코드 및 / 또는 예외를 사용할 수있는 다른 함수를 통해 객체를 생성하도록 강제합니다.
    • if (X* p = x_factory()) ...
    • Smart_Ptr_Throws_On_Null_Deref p_x = x_factory (); </li> <li>X x; // 사용할 수 없음; if (init_x (& x)) ...`
    • 기타...

간단히 말해서 C ++는 이러한 종류의 문제에 대한 우아한 솔루션을 제공하도록 설계되었습니다.이 경우 예외입니다. 인위적으로 그것을 사용하지 못하도록 제한한다면, 절반의 좋은 일을하는 다른 무언가가있을 것이라고 기대하지 마십시오.

(PS 나는 포인터에 의해 수정 될 변수를 전달하는 것을 좋아합니다- worked위와 같이-나는 FAQ lite가 그것을 권장하지 않지만 추론에 동의하지 않는다는 것을 알고 있습니다. FAQ에서 다루지 않는 것이 없다면 그것에 대한 토론에는 특별히 관심이 없습니다.)


이 특정 상황에 대한 제 제안은 파일을 열 수 없기 때문에 생성자가 실패하는 것을 원하지 않는다면 그 상황을 피하는 것입니다. 이미 열려있는 파일을 생성자에 전달하면 원하는 경우 실패 할 수 없습니다.


새로운 C ++ 표준은 이것을 여러 가지 방법으로 재정의하므로이 질문을 다시 검토 할 때입니다.

최선의 선택 :

  • 명명 된 선택 사항 : 최소한의 개인 생성자와 명명 된 생성자 : static std::experimental::optional<T> construct(...). 후자는 멤버 필드를 설정하고 불변성을 보장하며 확실히 성공할 경우에만 개인 생성자를 호출합니다. 개인 생성자는 멤버 필드 만 채 웁니다. 옵션을 테스트하기 쉽고 저렴합니다 (좋은 구현에서는 복사본도 절약 할 수 있음).

  • 기능적 스타일 : 좋은 소식은 (이름이 지정되지 않은) 생성자는 결코 가상이 아니라는 것입니다. 따라서 생성자 매개 변수와 별도로 두 개 이상의 람다 (성공한 경우 하나, 실패한 경우 하나)를 취하는 정적 템플릿 멤버 함수로 대체 할 수 있습니다. '실제'생성자는 여전히 비공개이며 실패 할 수 없습니다. 이것은 과잉으로 들릴 수 있지만 람다는 컴파일러에 의해 훌륭하게 최적화됩니다. if이 방법으로 옵션을 절약 할 수도 있습니다 .

좋은 선택 :

  • 예외 : 다른 모든 방법이 실패하면 예외를 사용합니다.하지만 정적 초기화 중에는 예외를 포착 할 수 없습니다. 가능한 해결 방법은이 경우 함수의 반환 값이 개체를 초기화하도록하는 것입니다.

  • 빌더 클래스 : 구성이 복잡한 경우 유효성 검사를 수행하고 작업이 실패 할 수 없을 때까지 일부 전처리를 수행하는 클래스가 있습니다. 상태 (예, 오류 기능)를 반환하는 방법을 갖도록합니다. 저는 개인적으로 스택 전용으로 만들 것이므로 사람들이 전달하지 않을 것입니다. 그런 다음 .build()다른 클래스를 구성 하는 메서드를 갖도록합니다 . 빌더가 친구 인 경우 생성자는 비공개 일 수 있습니다. 이 생성자가 빌더에 의해서만 호출된다는 것이 문서화되도록 빌더 만이 구성 할 수있는 것을 취할 수도 있습니다.

잘못된 선택 : (하지만 여러 번 보임)

  • 플래그 : '유효하지 않은'상태로 인해 클래스 불변성을 엉망으로 만들지 마십시오. 이것이 바로 우리가 optional<>. optional<T>유효하지 T않을 있다고 생각하십시오 . 유효한 객체에서만 작동하는 (멤버 또는 전역) 함수는 T. 확실히 유효한 작업을 반환하는 T. 유효하지 않은 객체 반환을 반환 할 수 있습니다 optional<T>. 객체를 무효화 할 수있는 것은 non-const optional<T>&또는 optional<T>*. 이 방법을 사용하면 객체가 유효한지 모든 함수를 확인할 필요가 없습니다 (그리고 이러한 함수 if는 약간 비쌀 수 있음). 생성자에서도 실패하지 않습니다.

  • 기본 구성 및 설정자 : 이것은 기본적으로 플래그와 동일하지만 이번에는 변경 가능한 패턴을 가져야합니다. setter는 잊어 버리세요. 클래스 불변을 불필요하게 복잡하게 만듭니다. 수업을 단순하게 구성하는 것이 아니라 단순하게 유지하는 것을 잊지 마십시오.

  • 기본 구성이며 init()ctor 매개 변수를 사용합니다 . 이것은를 반환하는 함수보다 낫지 optional<>않지만 두 가지 구성이 필요하고 불변성을 엉망으로 만듭니다.

  • Takebool& succeed : 이것은 우리가 전에했던 일이었습니다 optional<>. 그 이유 optional<>는 우월합니다. 실수로 (또는 부주의하게!) succeed플래그를 무시 하고 부분적으로 구성된 객체를 계속 사용할 수 없습니다 .

  • 포인터를 반환하는 팩토리 : 개체를 동적으로 할당하기 때문에 덜 일반적입니다. 주어진 유형의 관리 포인터를 반환하거나 (따라서 할당 / 범위 지정 스키마를 제한) naked ptr을 반환하고 클라이언트가 누출 될 위험이 있습니다. 또한 성능 측면에서 이동 회로도를 사용하면 바람직하지 않을 수 있습니다 (스택에 보관할 때 로컬은 매우 빠르고 캐시 친화적 임).

예:

#include <iostream>
#include <experimental/optional>
#include <cmath>

class C
{
public:
    friend std::ostream& operator<<(std::ostream& os, const C& c)
    {
        return os << c.m_d << " " << c.m_sqrtd;
    }

    static std::experimental::optional<C> construct(const double d)
    {
        if (d>=0)
            return C(d, sqrt(d));

        return std::experimental::nullopt;
    }

    template<typename Success, typename Failed>
    static auto if_construct(const double d, Success success, Failed failed = []{})
    {
        return d>=0? success( C(d, sqrt(d)) ): failed();
    }

    /*C(const double d)
    : m_d(d), m_sqrtd(d>=0? sqrt(d): throw std::logic_error("C: Negative d"))
    {
    }*/
private:
    C(const double d, const double sqrtd)
    : m_d(d), m_sqrtd(sqrtd)
    {
    }

    double m_d;
    double m_sqrtd;
};

int main()
{
    const double d = 2.0; // -1.0

    // method 1. Named optional
    if (auto&& COpt = C::construct(d))
    {
        C& c = *COpt;
        std::cout << c << std::endl;
    }
    else
    {
        std::cout << "Error in 1." << std::endl;
    }

    // method 2. Functional style
    C::if_construct(d, [&](C c)
    {
        std::cout << c << std::endl;
    },
    []
    {
        std::cout << "Error in 2." << std::endl;
    });
}

클래스 생성자에서 파일을 열고 싶습니다.

거의 확실히 나쁜 생각입니다. 구성 중에 파일을 열 때 적절한 경우는 거의 없습니다.

개봉이 실패하여 개체 구성이 완료되지 않을 수 있습니다. 이 실패를 처리하는 방법? 예외를 버리시겠습니까?

네, 그게 방법입니다.

이것이 가능하다면 던지지 않는 생성자에서 처리하는 방법은 무엇입니까?

Make it possible that a fully constructed object of your class can be invalid. This means providing validation routines, using them, etc...ick


One way is to throw an exception. Another is to have a 'bool is_open()' or 'bool is_valid()' functuon that returns false if something went wrong in the constructor.

Some comments here say it's wrong to open a file in the constructor. I'll point out that ifstream is part of the C++ standard it has the following constructor:

explicit ifstream ( const char * filename, ios_base::openmode mode = ios_base::in );

It doesn't throw an exception, but it has an is_open function:

bool is_open ( );

A constructor may well open a file (not necessarily a bad idea) and may throw if the file-open fails, or if the input file does not contain compatible data.

It is reasonable behaviour for a constructor to throw an exception, however you will then be limited as to its use.

  • You will not be able to create static (compilation unit file-level) instances of this class that are constructed before "main()", as a constructor should only ever be thrown in the regular flow.

  • This can extend to later "first-time" lazy evaluation, where something is loaded the first time it is required, for example in a boost::once construct the call_once function should never throw.

  • You may use it in an IOC (Inversion of Control / Dependency Injection) environment. This is why IOC environments are advantageous.

  • Be certain that if your constructor throws then your destructor will not be called. So anything you initialised in the constructor prior to this point must be contained in an RAII object.

  • More dangerous by the way can be closing the file in the destructor if this flushes the write buffer. No way at all to handle any error that may occur at this point properly.

You can handle it without an exception by leaving the object in a "failed" state. This is the way you must do it in cases where throwing is not permitted, but of course your code must check for the error.

참고URL : https://stackoverflow.com/questions/4989807/how-to-handle-failure-in-constructor-in-c

반응형