How to handle incorrect values in a constructor?

2019-01-25 03:00发布

Please note that this is asking a question about constructors, not about classes which handle time.

Suppose I have a class like this:

class Time
{
protected:
    unsigned int m_hour;
    unsigned int m_minute;
    unsigned int m_second;
public:
    Time(unsigned int hour, unsigned int minute, unsigned int second);
};

While I would want a to be constructed successfully, I would want the constructor of b to fail.

Time a = Time(12,34,56);
Time b = Time(12,34,65); // second is larger than 60

However, this is not possible, because constructors do not return any values and will always succeed.

How would the constructor tell the program that it is not happy? I have thought of a few methods:

  1. have the constructor throw an exception, and have handlers in the calling function to handle it.
  2. have a flag in the class and set it to true only if the values are acceptable by the constructor, and have the program check the flag immediately after construction.
  3. have a separate (probably static) function to call to check the input parameters immediately before calling the constructor.
  4. redesign the class so that it can be constructed from any input parameters.

Which of these methods is most common in industry? Or is there anything I may have missed?

11条回答
三岁会撩人
2楼-- · 2019-01-25 03:09

The typical solution is to throw an exception.

The logic behind that is the following: the constructor is a method that transforms a chunk of memory into a valid object. Either it succeeds (finishes normally) and you have a valid object or you need some non-ignorable indicator of a problem. Exceptions are the only way to make the problem non-ignorable in C++.

查看更多
何必那么认真
3楼-- · 2019-01-25 03:12

Another alternative, for completeness:

  • Redesign the interface so that invalid values are "impossible"

In your "Time" class, for example, you could have:

class Time{
public:
    Time(Hours h, Minutes m, Seconds s);
//...
};

Hours, Minutes and Seconds being bounded values. For example, with the (not yet)Boost Constrained Value library:

typedef bounded_int<unsigned int, 0, 23>::type Hours;
typedef bounded_int<unsigned int, 0, 59>::type Minutes;
typedef bounded_int<unsigned int, 0, 59>::type Seconds;
查看更多
三岁会撩人
4楼-- · 2019-01-25 03:17

Consider a factory-like pattern for generating time objects:

static bool Time::CreateTime(int hour, int min, int second, Time *time) {
  if (hour <= 12 && hour >= 0 && min < 60 && min >= 0 && 
      second < 60 && second >= 0)  {
     Time t(hour, min, second);
     *time = t;
     return true;
  }
  printf("Your sense of time seems to be off");
  return false;
}

Time t;
if (Time::CreateTime(6, 30, 34, &t)) {
  t.time(); // :)
} else {
  printf("Oh noes!");
  return;
}

This makes the assumption that Time has:

  • a default constructor
  • a copy constructor
  • a copy assignment operator
查看更多
劫难
5楼-- · 2019-01-25 03:18

Normally I'd say (1). But if you find that callers are all surrounding the construction with try/catch, then you can provide the static helper function in (3) as well, since with a bit of preparation the exception can be made impossible.

There is another option, although it has significant consequences for coding style so should not be adopted lightly,

5) Don't pass the parameters into the constructor:

class Time
{
protected:
    unsigned int m_hour;
    unsigned int m_minute;
    unsigned int m_second;
public:
    Time() : m_hour(0), m_minute(0), m_second(0) {}
    // either return success/failure, or return void but throw on error,
    // depending on why the exception in constructor was undesirable.
    bool Set(unsigned int hour, unsigned int minute, unsigned int second);
};

It's called two-phase construction, and is used precisely in situations where it is undesirable or impossible for constructors to throw exceptions. Code using nothrow new, to be compiled with -fno-exceptions, is probably the classic case. Once you get used to it, it is slightly less annoying than you might at first think.

查看更多
我只想做你的唯一
6楼-- · 2019-01-25 03:19

There is one more possible way. I am not saying this is in any way preferred, only adding it for completeness:

Create a factory function that creates an instance of your class on the heap, and returns a null pointer if the creation fails.

This is not really appropriate with valuetype-like objects as dates, but there might be useful applications.

查看更多
神经病院院长
7楼-- · 2019-01-25 03:20
  • have the constructor throw an exception, and have handlers in the calling function to handle it.

Yes. Design by Contract and leave the precondition checking on, and in case of failure, throw an exception. No invalid times anymore.

  • have a flag in the class and set it to true only if the values are acceptable by the constructor, and have the program check the flag immediately after construction.

Maybe. Acceptable in complex cases but again, throw if your checking fails.

  • have a separate (probably static) function to call to check the input parameters immediately before calling the constructor.

Maybe. This is about telling whether input data are correct or not, and may be useful if telling that is nontrivial, but see above for how to react in case of invalid data.

  • redesign the class so that it can be constructed from any input parameters.

No. You would basically defer the problem.

查看更多
登录 后发表回答