July 24, 2019
As an embedded developer, I’m always on the lookout for new programming languages to create firmware with. I’ve recently started developing with a new language you may have heard of called C++! It’s an expansion on the slightly older C language and has these newfangled things called “objects”.
In all seriousness, embedded systems are still mostly made in C, which was itself first released in the 70s(!) and is still used today for its speed of execution and ease of use for manipulating low level hardware. However, because of developments in transistor size and cost, embedded systems have become powerful enough that the overhead C++ brings is no longer a factor in deciding between using C or C++. It’s for this reason my team at work has decided to use C++ for a new greenfield project we’re doing. It has brought its own share of challenges but overall has made development smoother and writing unit tests much easier with the addition of classes.
In order to learn while I go, I got a book titled “Effective C++: Third Edition”. It’s not exactly a new text and there are newer editions you can buy, but I got this one since a lot of people I saw online recommended starting with an older edition if you’re new to the language. In it, I found an especially useful bit of knowledge I’d like to share which is an interesting way of creating type safe interfaces for classes in C++.
As I and I’m sure anyone who has worked with legacy code can tell you, interfaces that can be misused WILL eventually be misused, and reducing that ability in any way possible is a good thing for the longevity of your code. This abuse could include anything from incorrect order of function parameters to typecasted variables to avoid enumerated type compilation errors. Here’s a quick example of a safer interface:
/*------------------------------------------*/
class Month
{
public:
static Month january(void) { return(Month(1)); }
static Month february(void) { return(Month(2)); }
static Month march(void) { return(Month(3)); }
static Month april(void) { return(Month(4)); }
static Month may(void) { return(Month(5)); }
static Month june(void) { return(Month(6)); }
static Month july(void) { return(Month(7)); }
static Month august(void) { return(Month(8)); }
static Month september(void) { return(Month(9)); }
static Month october(void) { return(Month(10)); }
static Month november(void) { return(Month(11)); }
static Month december(void) { return(Month(12)); }
uint32_t localMonth;
private:
explicit Month(uint32_t month);
};
Month::Month(uint32_t month)
{
localMonth = month;
}
Above we have an interface class for creating valid month values. It essentially consists of a few statically defined methods that return an instance of the Month class with a valid month value (1-12). From here you can define your main class with a constructor parameter of a reference to the interface class:
*------------------------------------------*/
class Calendar
{
public:
explicit Calendar(const Month& month);
uint32_t startingMonth;
private:
};
Calendar::Calendar(const Month& month)
{
startingMonth = month.localMonth;
}
As you can see from the constructor, we simply set our localMonth variable we created in the the Month class to the startingMonth variable in our Calendar class.
int main()
{
Calendar calendar(Month::march());
cout << calendar.startingMonth << endl;
return 0;
}
And that’s it! Just pass in the selected month to your Calendar constructor and you’ve created a class interface that is basically impossible to misuse. If I see anything else that I find exceptionally useful I’ll post about it, but for more useful notes on well written C++, I’d strongly recommend Effective C++: Third and future editions!
Effective C++: Third Edition : https://amzn.to/2YboIDR
2 Comments
Add Yours →uint32_t localMonth should be const, else it defeats the purpose
Hmm good point. While the purpose of having statically defined functions that return a self constructed object reduces the mental burden of thinking about what value each month is, nothing is stopping you from calling any of these functions, saving the result, and then just setting the month to whatever valid number you want.
For the purpose of the example however, this won’t affect the value being used in the Calendar object as it’s just making a copy of whatever month had been currently assigned to.
One way to break this interface as it’s written would be to construct the Month object and then reassign its “localMonth” value before passing it into Calendar instead of doing it inline with the constructor! This would be a more intentional decision by the programmer to do, thus re-enforcing the “hard to use incorrectly” property of the interface.