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