5

I think, it is easier explain using an example. Let's take a class that model the speed of a Formula 1 car, the interface may look something like:

class SpeedF1
{
public:
  explicit SpeedF1(double speed);
  double getSpeed() const;
  void setSpeed(double newSpeed);
  //other stuff as unit 
private:
  double speed_;
};

Now, negative speed are not meaningful in this particular case and neither value greater then 500 km/h. In this case the constructor and the setSpeed function may throw exceptions if the value provide is not within a logical range.

I can introduce an extra layer of abstraction and insert a extra object instead of double. The new object will be a wrapper around the double and it will be construct and never modify. The interface of the class will be changed to:

class ReasonableSpeed
{
public:
  explicit ReasonableSpeed(double speed); //may throw a logic error
  double getSpeed() const;
  //no setter are provide
private:
  double speed_;
};

class SpeedF1
{
public:
  explicit SpeedF1(const ReasonableSpeed& speed);
  ReasonableSpeed getSpeed() const;
  void setSpeed(const ReasonableSpeed& newSpeed);
  //other stuff as unit 
private:
  ReasonableSpeed speed_;
};

With this design SpeedF1 cannot throw, however I need to extra pay an object constructor every time I want to reset the speed.

For class where a limited set of value are reasonable (for example the Months in a calendar) I usually make the constructor private and provide a complete set of static functions. In this case it is impossible, another possibility is implement a null object pattern but I am not sure it is superior to simply throw an exception.

Finally, my question is:

What is the best practise to solve this kind of problem?

Alessandro Teruzzi
  • 3,918
  • 1
  • 27
  • 41
  • 1
    A class that represents the velocity of the car? What are you trying to encapsulate? Also don't enforce things that are not physically/mathematically impossible, like `speed < 500km/h`, someone will build [built](http://en.wikipedia.org/wiki/ThrustSSC) a supersonic car and will file you a bug. – Yakov Galka Aug 27 '11 at 16:15
  • the class was just an example, as I said at the first line, think at any class that accept a real number between two values. The percentage is another example it is a real number between 0 and 100. – Alessandro Teruzzi Aug 27 '11 at 18:23

5 Answers5

6

First off, don’t overestimate the cost of the extra constructor. In fact, this cost should be exactly the cost of initialising a double plus the cost for the validity check. In other words, it is likely equal to using a raw double.

Secondly, lose the setter. Setters – and, to a lesser degree, getters – are almost always anti-patterns. If you need to set a new (maximum) speed, chances are you actually want a new car.

Now, about the actual problem: a throwing constructor is completely fine in principle. Don’t write convoluted code to avoid such a construct.

On the other hand, I also like the idea of self-checking types. This makes the best use of C++’ type system and I’m all in favour of that.

Both alternatives have their advantages. Which one is best really depends on the exact situation. In general, I try to exploit the type system and static type checking as much as possible. In your case, this would mean having an extra type for the speed.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 2
    "lose the setter... chances are you actually want a new car" That's a quite functional approach to look at things, but I am not sure whether liberal use of this style in C++ really has no adverse effects on performance. – kizzx2 Aug 27 '11 at 16:06
  • @Konrad Rudolph "lose the setter.Setters are almost always anti-patterns." I would like to learn more about this, do you have any good link (or book) on this topic? – Alessandro Teruzzi Aug 27 '11 at 16:10
  • @Alessandro Felleisen has a talk on teaching this type of design http://www.ccs.neu.edu/home/matthias/Presentations/FDPE2005/htdch.pdf and a book draft. http://www.ccs.neu.edu/home/matthias/htdc.html – antonakos Aug 27 '11 at 16:55
  • 3
    "If you need to set a new speed, chances are you actually want a new car." Seriously? I am probably the most "functional" programmer in my office, and even I think that sounds insane. I can change speeds in my actual car without buying a new one; why should the object representing my car be different? – Nemo Aug 27 '11 at 17:13
  • 1
    @Nemo I agree, "chances are you want a new car" is ridiculous. To add a little to the answer, I'd also say that if people see something thrown in the constructor, they know that they constructed the object wrong. But if some other member function throws, it's not clear that they constructed it wrong because they might just have used the function that threw wrong. – Seth Carnegie Aug 27 '11 at 17:50
  • @kizzx2 Of course that depends. If you have to change the speed often, it *may* have adverse effects. But normally, immutable structures work well even in high-performance settings, and the optimiser can do a lot of work here. For instance, having immutable `Point` objects in a physics/graphics environment works like a charm. Fundamentally, setters are a violation of encapsulation. See my updated answer for a reference. – Konrad Rudolph Aug 28 '11 at 08:16
  • @Nemo I understood the `speed` to represent the *maximum* speed of the care. If it’s the current speed, then constructing a new car may be overkill. But then I’d use a method instead of a setter. See my answer for a reference justifying the claim that setters are harmful. – Konrad Rudolph Aug 28 '11 at 08:18
  • @Konrad Rudolph With my experience with C++ in particular, whose design largely restrict possible compiler optimizations (there are little to none guarantees the compiler gets), I still doubt whether we can trust the compiler so much. I agree with the functional perspective, but "avoid setters as much as possible" may not yet fit our current generation of imperative languages. [This is another SO post](http://stackoverflow.com/questions/3664272/stdvector-is-so-much-slower-than-plain-arrays) that may inspire how little the compiler can help _even_ with some care taken on the programmer's part. – kizzx2 Aug 28 '11 at 13:36
  • @kizzx2 The other thread showcases the cost of calling 1000 constructor – *nothing more*, in particular not the cost of arrays versus vectors. The only reason that `malloc` is faster than `std::vector` is that it works with uninitialised memory (this only works because your structure is a POD). The *access* in vectors and raw arrays is *identical*. In fact, modern C++ compilers contain incredibly smart optimisers. I’m working on a library that relies heavily on the capability of the compiler to perform optimisations, and our experience shows that this is a very reasonable assumption to make. – Konrad Rudolph Aug 28 '11 at 13:58
  • @Konrad Rudolph at the risk of trolling, I guess I'll just add some closing notes. _Axiom_: Cloning an object triggers memory copying; _axiom_: in-place update is faster than cloning. I said "axioms" instead of laws because FP langauges continue to challenge them. In the case of day to day C++ programming though, I think we are still stuck with those axioms. Unless some research paper comes up and _proves_ that compilers can optimize those _axioms_ in C++, and tests come up to _show_ that compilers actually perform those optimizations, I'd rather stay on the safe side. – kizzx2 Aug 31 '11 at 11:52
  • @Konrad Rudolph For the other thread for example, it was painfully obvious (to me) that the nullary constructor of `Pixel` is no-op, yet the compiler insisted on calling it even with ful optimization. Again, the point here is "the whole C++ community has been keen on avoiding copying (pass-by-reference, rvalue-references, etc.)" I was just pointing out that it's just not the traditional wisdom right now. (not to say we must stick to dogma, just to let readers know what kind of things he's dealing with if he goes against the flow). – kizzx2 Aug 31 '11 at 11:57
  • @Konrad Rudolph Interestingly, I actually would _advocate_ your answer if the speed is interpreted as maximum speed (I thought it was current speed), but anyway... – kizzx2 Aug 31 '11 at 12:07
1

I strongly vote for the second option. This is only my personal opinion without a lot of academic backing. My experience is that setting up a "pure" system that operates on only valid data makes your code a lot cleaner. This can be achieved by using your second approach which ensures that only valid data enters the system.

If your system grows, you may find that ReasonableSpeed gets used in a lot of places (use your discretion, but chances are things actually get reused quite a lot). The second approach will save you a lot of error checking codes in the long term.

kizzx2
  • 18,775
  • 14
  • 76
  • 83
  • I agree with the overall argument (the “pure system” part especially). On the other hand, having an entity represent speed *may* be overkill. – Konrad Rudolph Aug 28 '11 at 08:19
1

If only one class inherits from ReasonableSpeed then it seems a bit of an overkill.

If many classes inherit from, or use ReasonableSpeed, then it's smart.

Steve Wellens
  • 20,506
  • 2
  • 28
  • 69
0

Both of your designs yield the same result when an invalid value is used as speed, i.e. they both throw an exception. Applying Occam's razor principle, or Unix Rule of Rarsimony:

Rule of Parsimony: Write a big program only when it is clear by demonstration that nothing else will do.

‘Big’ here has the sense both of large in volume of code and of internal complexity. Allowing programs to get large hurts maintainability. Because people are reluctant to throw away the visible product of lots of work, large programs invite overinvestment in approaches that are failed or suboptimal.

you may like to pick the first simpler approach. Unless you'd like to reuse ReasonableSpeed class.

Community
  • 1
  • 1
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

I would recommend doing this instead:

class SpeedF1
{
public:
  explicit SpeedF1(double maxSpeed);
  double getSpeed() const;
  void accelerate();
  void decelerate();
protected:
  void setSpeed(double speed);
  //other stuff as unit 
private:
  double maxSpeed_;
  double curSpeed_;
};

SpeedF1::SpeedF1(double maxSpeed) maxSpeed_(maxSpeed), curSpeed_(0.0) { }

double SpeedF1::getSpeed() const { return curSpeed_; }

void SpeedF1::setSpeed(double speed) {
    if(speed < 0.0) speed = 0.0;
    if(speed > maxSpeed_) speed = maxSpeed_;
    curSpeed = speed;
}

void SpeedF1::accelerate() {
    setSpeed(curSpeed_ + SOME_CONSTANT_VELOCITY);
}


void SpeedF1::decelerate() {
    setSpeed(curSpeed_ - SOME_CONSTANT_VELOCITY);
}
Casey
  • 10,297
  • 11
  • 59
  • 88