What are invariants, how can they be used, and have you ever used it in your program?
In OOP, an invariant is a set of assertions that must always hold true during the life of an object for the program to be valid. It should hold true from the end of the constructor to the start of the destructor whenever the object is not currently executing a method that changes its state.
An example of invariant could be that exactly one of two member variables should be null. Or that if one has a given value, then the set of allowed values for the other is this or that…
Source:
artima - The C++ Style Sweet Spot
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:
Take a
vectoras a very simple example. Avectorknows 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 withget_nameandset_addressandget_nameandset_namefunctions. Or even worse, make a virtual base class with virtualget_nameandset_namefunctions 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 itpersonnel_recordormailing_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.
Designing Simple Interfaces
Bill Venners: You said that the invariant helps you decide what goes into the interface. Could you elaborate on how? Let me attempt to restate what you said, and see if I understand it. The functions that are taking any responsibility for maintaining the invariant should be in the class.
Bjarne Stroustrup: Yes.
Bill Venners: Anything that’s just using the data, but not defending the invariant, doesn’t need to be in the class.
Bjarne Stroustrup: Let me give an example. There are some operations you really can’t do without having direct access to the representation. If you have an operation that changes the size of a
vector, then you’d better be able to make changes to the number of elements stored. You move the elements and change the size variable. If you’ve just got to read the size variable, well, there must be a member function for that. But there are other functions that can be built on top of existing functions. For example, given efficient element access, a find function for searching in a vector is best provided as a non-member.Another example would be a
Dateclass, where the operations that actually change the day, month, and year have to be members. But the function that finds the next weekday, or the next Sunday, can be put on top of it. I have seenDateclasses with 60 or 70 operations, because they built everything in. Things likefind_next_Sunday. Functions like that don’t logically belong in the class. If you build them in, they can touch the data. That means if you want to change the data layout, you have to review 60 functions, and make changes in 60 places.Instead, if you build a relatively simple interface to a
Dateclass, you might have five or ten member functions that are there because they are logically necessary, or for performance reasons. It’s hard for me to imagine a performance reason for aDate, but in general that’s an important concern. Then you get these five or ten operations, and you can build the other 50 in a supporting library. That way of thinking is fairly well accepted these days. Even in Java, you have the containers and then the supporting library of static methods.
Related Concepts
- SOLID Principles - Grounded in invariant maintenance
- Tell, Don’t Ask - Objects protect their invariants
- Constraints over Conventions - Enforcing invariants at the persistence layer, where callbacks can’t be bypassed