The Artima Developer Community
Sponsored Link

The C++ Style Sweet Spot
A Conversation with Bjarne Stroustrup, Part I
by Bill Venners
October 13, 2003

<<  Page 3 of 4  >>


Classes Should Enforce Invariants

Bjarne Stroustrup: My rule of thumb is that you should have a real class with an interface and a hidden representation if and only if you can consider an invariant for the class.

Bill Venners: What do you mean by invariant?

Bjarne Stroustrup: What is it that makes the object a valid object? An invariant allows you to say when the object's representation is good and when it isn't. Take a vector as a very simple example. A vector knows that it has n elements. It has a pointer to n elements. The invariant is exactly that: the pointer points to something, and that something can hold n elements. If it holds n+1 or n-1 elements, that's a bug. If that pointer is zero, it's a bug, because it doesn't point to anything. That means it's a violation of an invariant. So you have to be able to state which objects make sense. Which are good and which are bad. And you can write the interfaces so that they maintain that invariant. That's one way of keeping track that your member functions are reasonable. It's also a way of keeping track of which operations need to be member functions. Operations that don't need to mess with the representation are better done outside the class. So that you get a clean, small interface that you can understand and maintain.

Bill Venners: So the invariant justifies the existence of a class, because the class takes the responsibility for maintaining the invariant.

Bjarne Stroustrup: That's right.

Bill Venners: The invariant is a relationship between different pieces of data in the class.

Bjarne Stroustrup: Yes. If every data can have any value, then it doesn't make much sense to have a class. Take a single data structure that has a name and an address. Any string is a good name, and any string is a good address. If that's what it is, it's a structure. Just call it a struct. Don't have anything private. Don't do anything silly like having a hidden name and address field with get_name and set_address and get_name and set_name functions. Or even worse, make a virtual base class with virtual get_name and set_name functions and so on, and override it with the one and only representation. That's just elaboration. It's not necessary.

Bill Venners: It's not necessary because there's one and only representation. The justification is usually that if you make it a function, then you can change the representation.

Bjarne Stroustrup: Exactly, but some representations you don't change. You don't change the representation of an integer very often, or a point, of a complex number. You have to make design decisions somewhere.

And the next stage, where you go from the plain data structure to a real class with real class objects, could be that name and address again. You probably wouldn't call it name_and_address. You'll maybe call it personnel_record or mailing_address. At that stage you believe name and address are not just strings. Maybe you break the name down into first, middle, and last name strings. Or you decide the semantics should be that the one string you store really has first, middle, and last name as parts of it. You can also decide that the address really has to be a valid address. Either you validate the string, or you break the string up into first address field, second address field, city, state, country, zip code, that kind of stuff.

When you start breaking it down like that, you get into the possibilities of different representations. You can start deciding, does it really add to have private data, to have a hierarchy? Do you want a plain class with one representation to deal with, or do you want to provide an abstract interface so you can represent things in different ways? But you have to make those design decisions. You don't just randomly spew classes and functions around. And you have to have some semantics that you are defending before you start having private data.

The way the whole thing is conceived is that the constructor establishes the environment for the member functions to operate in, in other words, the constructor establishes the invariant. And since to establish the invariant you often have to acquire resources, you have the destructor to pull down the operating environment and release any resources required. Those resources can be memory, files, locks, sockets, you name it—anything that you have to get and put back afterwards.

<<  Page 3 of 4  >>

Sponsored Links

Copyright © 1996-2018 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use