On the Importance of Fitting in


Programming in a object oriented language can be seen as an exercise in extending the type system. And if all your code is wrapped nicely in classes and functions, what’s left is just combining those using the language. Simple, right?

Seen from this viewpoint, the importance of designing your types correctly become very important. And the best way to design them correctly, is to have them behave as much as possible as the built-in types and library types. (On a side note, this is one reason I dislike Java’s lack of operator overloading.)

As an example, say I am designing an embedded system for a car stereo. Every radio-station is stored in a RadioStation class. There is also a RadioStationContainer class that manages the radiostations. Now we need a function to add RadioStations to the container. What do we name it? What name will make a good interface for the user of this library? addRadioStation()?

I would say a much better name is push_back(). Even though you might think addRadioStation() sounds like a more intuitive name, if you are making a container, I’d argue having it behave like all other containers is more intuitive.

How about allowing people to iterate over radio stations? The iterator type will depend on the type of container RadioStationContainer is using internally. One method I’ve seen is people use something like this (oustide the RadioStationContainer class): typedef std::list<RadioStation> RSCit. This gives people a short an easy name for the iterator, right? Again I would argue you should instead make a normal typedef inside the class, so people can use the normal RadioStationContainer::iterator. If they need a shorthand, they can make their own typedef.

Here is an example of a RadioStationContainer that can be used as a normal container:

class RadioStationContainer {
public:
    //Define the normal iterator types the user will expect
    typedef list<RadioStation>::iterator iterator;
    typedef list<RadioStation>::const_iterator const_iterator;

    //Default constructor and copy constructor
    RadioStationContainer() {}
    RadioStationContainer(const RadioStationContainer& rc) {
        copy(rc.begin(), rc.end(), back_inserter(stations));
    }

    //push_back() defined with the normal container interface
    void push_back(const RadioStation& s) { stations.push_back(s); }

    //iterators for working with both const and non const RadioStationContainers
    iterator begin() { return stations.begin(); }
    iterator end() { return stations.end(); }
    const_iterator begin() const { return stations.begin(); }
    const_iterator end() const { return stations.end(); }

private:
    list<RadioStation> stations;

};

This will fit nicely with how a user of the library expects a container to behave. But there is more! This will also fit very nicely with how the Standard Template Library expects a container to behave! You have already seen an example, using copy and back_inserter in the copy constructor. But now the user is also free to use transform, for_each etc:

void doStuffWithStation(RadioStation& s);

void f(RadioStationContainer& rc) {
    for_each(rc.begin(), rc.end(), doStuffWithStation);
}

So when in doubt, always try to fit in.

6 Responses to “On the Importance of Fitting in”

  1. Asbjørn Ulsberg Says:

    This is where interfaces makes not fitting in impossible and thus the perfect solution. In C++, that would mean declaring a Container class with virtual members assigned to ‘0’ that then needs to be overridden in the derived class.

    In C# and Java, you have an even more explicit way of doing this through the “interface” keyword, which I think have a rightful home in any statically typed language. It at least solves the problem on fitting in neatly.

    • Anders Schau Knatten Says:

      The thing is, C++ doesn’t like to impose inheritance when it’s not needed. If std::list implemented a (set of) interface(s), you would introduce inheritance, avtbl, pointer indirection and code would be slower. This is one of the arguments why generic programming works the way it does in C++.

      To be a container, you only have to implement an implicit interface. If you have forward iterators you can be used in for_each, if you have random access iterators, you can be sorted.

      There is however (for efficiency reasons) no explicit way to state this.

      C++0x for a while had Concepts, which aimed to introduce (without a runtime overhead) a bit more explicit type safety into the duck typing which is generic programming, but it didn’t make it to the final draft.

      • Asbjørn Ulsberg Says:

        While most C++ programmers might consider “implicit” interfaces as a feature, I see it as a problem. As you so wisely told me on Friday; “When C++ is your hammer, everything starts to look like your thumb.” ;-)

        • Anders Schau Knatten Says:

          I think most C++ programmers will agree that there are some big drawbacks with this as well, which is why we had the Concepts proposal for C++0x.

          But if you see “implicit interfaces” (here, ducktyping) as a problem in general, are you also against dynamic languages like Python and Ruby (there, dynamic typing)? Their interfaces are even more implicit that the C++ template ones, which at least are checked during compilation (even though compiler output is horrible).

          • Asbjørn Ulsberg Says:

            I don’t see duck typing as a problem; that is indeed a huge feature of dynamic languages. What I see as a problem is not having a mechanism in a statically typed language to put proper, understandable constraints on the types that are to be extended and used in e.g. templates.

            In dynamic languages, the whole mind set is so different that the problem becomes different too, although there are a lot of proponents of introducing classes and interfaces to JavaScript, for instance.

            As I’ve told you before; take a look at C#, that in v4.0 adds support for the “dynamic” keyword so you can duck type when you want, but when you do, you are conscious about it, but when you don’t, all the static type safeguards are there, including interfaces, type constraints, etc., that will smack you in the face compile-time, if you try to break them.

            • Anders Schau Knatten Says:

              I should probably not have used the phrase “duck typing”, as that is usually associated with dynamic type systems.

              The duck typing in C++ templates is static. So your program will never even compile if the type you are using as a template argument doesn’t provide the correct interface. This ensures that you never leave the normal C++ static paradigm.

              The only problem I see with this non-declarative static duck typing is that compiler output can be a huge mess. (This is by no means a small issue, in fact it is probably the thing that annoys me the most with C++. (And that says a lot!:)))


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: