How to handle a crash in a constructor in C ++?

I want to open a file in the class constructor. Perhaps the discovery could fail, then the construction of the facility could not be completed. How to deal with this failure? Throw an exception? If possible, how to handle it in the constructor without a throw?

+60
c ++
Feb 14 2018-11-11T00:
source share
7 answers

If the object's construction fails, throw an exception.

The alternative is terrible. You will need to create a flag if the construct is completed, and check it in each method.

+36
Feb 14 2018-11-11T00:
source share

I want to open a file in the class constructor. Perhaps the discovery could fail, then the construction of the facility could not be completed. How to deal with this failure? Throw an exception?

Yes.

If possible, how to handle it in the constructor without a throw?

Your options:

  • redesign the application so that it does not need constructors, so that it does not rush - really, do it if possible
  • add a flag and verify successful construction
    • you can have every member function that can be legitimately called right after the constructor checks the flag, ideally throwing if it is set, but otherwise returns an error code
      • It is ugly and difficult to maintain the right if you have an unstable group of developers working on code.
      • You can get some compile-time checking if an object is polymorphically related to either of two implementations: a successfully constructed one and always errors, but this leads to heap use and performance.
    • You can transfer the burden of checking the flag from the called code to the called party by documenting the requirement that they call some "is_valid ()" or similar function before using the object: again prone to errors and ugly, but even more common, unacceptable and uncontrollable.
      • You can make it a little easier and more localized for the caller if you support something like: if (X x) ... (i.e. the object can be evaluated in a boolean context, usually by providing operator bool() const or similar integral conversion), but then you do not have x in the request area for detailed error information. This can be known from, for example, if (std::ifstream f(filename)) { ... } else ...;
  • the caller has a thread for which they are responsible for opening ... (known as Injection Dependency or DI) ... in some cases this does not work so well:
    • You may still have errors when using the stream inside your constructor, then what?
    • the file itself may be a part of the implementation, which should be closed to your class, and not be exposed to a call: what if you want to delete this requirement later? For example: you may have read the search table of pre-calculated results from a file, but made your calculations so fast that there is no need for pre-calculation - it is painful (sometimes even impractical in a corporate environment) to delete a file at all points using the client, and there is also much more recompilation than potentially just pulling.
  • force the caller to provide a buffer for the success / failure / condition variable that the constructor sets: for example. bool worked; X x(&worked); if (worked) ...
    • this burden and verbosity attracts attention and, we hope, makes the caller much more conscious of the need to consult the variable after constructing the object
  • force the caller to construct the object through some other function that can use return codes and / or exceptions:
    • if (X* p = x_factory()) ...
    • Smart_Ptr_Throws_On_Null_Deref p_x = x_factory (); </li> <li> X x; // can never be used; if (init_x (& x)) ... `
    • etc...

In short, C ++ is designed to provide elegant solutions to such problems: exceptions in this case. If you artificially restrict yourself from using them, then do not expect that there will be something else that does half the good work.

(PS I like to pass variables that will be changed by the pointer - as stated above worked ). I know that frequently asked questions often reject him, but disagree with the reasoning. It's not particularly interesting to discuss this unless you have something that is not covered by the FAQ.)

+26
Feb 14 '11 at 8:19
source share

My suggestion for this particular situation is that if you do not want the constuctor to fail, because if he cannot open the file, then avoid this situation. Pass to the constructor already open file, if this is what you want, then it cannot fail ...

+15
Feb 14 2018-11-11T00:
source share

The new C ++ standard redefines this in different ways to return to this question.

The best options:

  • Named optional: have a minimal private constructor and a named constructor: static std::experimental::optional<T> construct(...) . The latter tries to set up member fields, ensures invariance, and calls only the private constructor if it certainly succeeds. The private constructor fills only the fields of the element. It is easy to test optionally and inexpensively (even a copy can be saved in a good implementation).

  • Functional style: The good news is that (unnamed) constructors are never virtual. Therefore, you can replace them with a static member function of the template, which, in addition to the constructor parameters, accepts two (or more) lambdas: one if it was successful, one if it failed. The "real" constructor is still private and cannot help but work. This may seem redundant, but lambdas are beautifully optimized by compilers. You might even get rid of the if optional of this method.

A good choice:

  • Exception: if all else fails, use an exception - but note that you cannot catch an exception during static initialization. A possible workaround is that in this case, the return value function initializes the object.

  • Builder class: if the construction is complex, you have a class that performs validation and possibly some preprocessing to such an extent that the operation cannot fail. Let him have a way to return the status (yep, error function). I personally would make it only for the stack, so people will not pass by it; then let it have a .build() method that creates another class. If the constructor is a friend, the constructor may be private. He may even take something that only the builder can build, so that he documents that this constructor should only be called by the builder.

Bad choices: (but seen many times)

  • Flag: Do not spoil your class invariant with an “invalid” state. That is why we have optional<> . Think of optional<T> , which may be invalid, T , which cannot. A function (member or global) that works only on real objects works on T One that confidently returns valid jobs on T One that may return an invalid object return optional<T> . One that can invalidate an object accepts a non-constant optional<T>& or optional<T>* . This way, you will not need to check every function that is valid for your object (and those that if can become a little expensive), but then also do not interrupt the constructor.

  • Default construction and setters: this is basically the same as the flag, only this time you are forced to have a mutable template. Forget about setters, they unnecessarily complicate your class invariant. Remember that your class is simple, not simple.

  • The default construct is init() , which accepts ctor parameters: this is nothing better than a function that returns optional<> , but requires two constructs and will interfere with your invariant.

  • Take bool& succeed : That was what we did before optional<> . The optional<> reason is above, you cannot mistakenly (or carelessly!) Ignore the succeed flag and continue to use the partially constructed object.

  • Factory, which returns a pointer: this is less general because it makes the object stand out dynamically. Either you return the specified type of managed pointer (and, therefore, limit the distribution / scaling scheme), or return null ptr and risk clients. In addition, with the transition of the scheme in terms of performance, this may become less desirable (local residents, when they are stored on the stack, are very fast and convenient for caching).

Example:

 #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; }); } 
+7
Aug 15 '16 at 20:04 on
source share

I want to open a file in the class constructor.

Almost certainly a bad idea. There are very few cases when opening a file during construction.

<i> Perhaps the discovery may fail, then the construction of the facility cannot be completed. How to deal with this failure? Throw an exception?

Yes, it will be so.

<i> If possible, how to handle it in the constructor without a throw?

Make it possible for a fully constructed object of your class to be invalid. This means providing test routines, using them, etc. .... ick

+4
Feb 14 2018-11-11T00:
source share

One way is to throw an exception. Another is the presence of the bool is_open () or bool is_valid () function, which returns false if something went wrong in the constructor.

Some comments say that it is incorrect to open the file in the constructor. I will 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 does not throw an exception, but it has an is_open function:

 bool is_open ( ); 
+4
Feb 14 2018-11-11T00:
source share

The designer can open the file well (not necessarily a bad idea) and can throw it if the file file fails, or if the input file does not contain compatible data.

Constructor behavior is reasonable to throw exceptions, but you will be limited with regard to its use.

  • You cannot create instances of the static (compilation file level) of this class that are created before "main ()", since the constructor should only ever be set in a regular thread.

  • This may extend to a later “first” lazy evaluation when something is loaded as soon as necessary, for example, in the boost :: once construct, the call_once function should never be thrown.

  • You can use it in an IOC environment (control / dependency inversion). This is why the IOC environment is profitable.

  • Be sure that if your constructor throws, your destructor will not be called. So, everything that you initialized in the constructor to this point should be contained in the RAII object.

  • More dangerous could be closing the file in the destructor if this flushes the write buffer. In no case do you need to handle any errors that may occur at this point properly.

You can handle this without exception by leaving the object in a failed state. So you should do this in cases where throwing is prohibited, but, of course, your code should check for an error.

+4
Feb 14 '11 at 8:15
source share



All Articles