Introduction to an advanced language
After C, a language with objects lets you think in pieces that fit together. It is not a fad: it is how a ten-thousand-line project stays understandable.
01Why functions are not enough
In C a program grows as a list of functions that pass structures to each other. It works until the
project reaches a few thousand lines and three problems appear: anyone can touch any field,
the same piece of data is validated in twenty places, and adding a case forces you to hunt down all the
scattered ifs.
- Encapsulation. The data and the operations that touch it live together, and from outside you only see what was chosen to be shown. If the state can only be changed through methods, it is always valid.
- Inheritance. A class starts from another and adds or changes what it needs, without repeating what is common.
- Polymorphism. The same method is called on different objects and each one does its own thing. It is what lets you add a new instrument without touching the code that uses them.
It is neither a fad nor an obligation: it is a way to keep a ten-thousand-line project understandable. For a two-hundred-line firmware, C and a structure remain the right answer.
02From structure to class
| In C | In C++ or Python |
|---|---|
struct Channel with its fields | The class, with the same private fields |
channel_init(&c, ...) | The constructor |
channel_read(&c) | A method: the object is the implicit first parameter |
channel_free(&c) | The destructor, or the with block |
| A function pointer in the structure | A virtual method |
class Channel:
def __init__(self, name, scale):
self.name = name
self._scale = scale # the underscore says “do not touch me”
def read(self):
return self._raw() * self._scale
That explicit self in Python is the same as the implicit this in C++ and the same
pointer to the structure that was passed by hand in C. The syntax changes, not the mechanism.
The same code calls measure() on different objects. Choose which one it is and see which implementation runs.
03Dynamic dispatch
When a method is called on an object, the language has to decide which of the implementations to run. If that decision is made at compile time, it is static dispatch; if it is made at run time, by looking at the object’s real type, it is dynamic, and that is what makes polymorphism possible.
for device in bench: # multimeter, oscilloscope, power supply…
print(device.describe()) # each one answers in its own way
The loop does not know and does not care what is inside the list. Adding a new instrument does not force you to touch it: it is enough for the new class to implement the same methods. That is what you gain.
In C++, each class with virtual methods has a virtual function table, and each object stores a pointer to its class’s table. Calling a virtual method means reading that pointer, looking up the entry and jumping: two more indirections than an ordinary call. In Python it is even more dynamic —the name is looked up in a dictionary— and that is why it is more flexible and slower.
04Abstract types and exceptions
An abstract data type is defined by what can be done with it, not by how it is built. A stack is “push, pop and look at the top”, regardless of whether inside there is an array or a linked list. The standard library is exactly that: tested containers that no longer have to be written.
| Need | C++ | Python |
|---|---|---|
| A sequence that grows | std::vector | list |
| Look up by key | std::map, unordered_map | dict |
| No duplicates | std::set | set |
| Double-ended queue | std::deque | collections.deque |
| Memory that frees itself | unique_ptr, shared_ptr | Garbage collector |
Exceptions
In C, a function reports an error by returning a code that almost nobody checks. Exceptions separate the normal path from the error path:
try:
v = instrument.measure()
except TimeoutError:
log("the device did not respond")
finally:
instrument.close() # this runs no matter what
Three rules: catch only what you know how to handle —an empty except hides programming
errors—, always release with finally or with with, and do not use
exceptions for normal flow, because they are expensive and confusing.
05Which one to use in electronics
| Job | Reasonable tool |
|---|---|
| Firmware for a small microcontroller | C, and C++ without exceptions or dynamic allocation |
| Large firmware, with layers | Modern C++: classes, templates, constexpr |
| Lab tool, interface, analysis | Python, for how quickly it can be written |
| Processing measurements and plotting | Python with NumPy and Matplotlib |
| Whatever has to fly | C or C++, or the critical part in C called from Python |
The most useful combination for an electronics engineer is to know both: C or C++ for the device and Python for everything around it —automating the bench, processing the measurements, putting together the report—. They do not compete: they are used in different layers of the same project.
06In the lab
Implement a stack in C with a structure and functions, and in C++ or Python as a class. Compare how many ways there are to misuse it in each case.
Define a base class Instrument with connect, measure and
describe, and three derived classes. Traverse a mixed list calling the same methods, and
then add a fourth class without touching the loop.
Write a function that reads the serial port and raises an exception if the device does not respond. Check
with finally that the port ends up closed even when it fails.
07Common mistakes
- Making classes for everything. If there is no state to protect, a function is enough.
- Deep inheritance. Four levels are harder to follow than a little repetition.
- Exposing every attribute and losing the one guarantee that encapsulation gave.
- Catching
Exceptionand carrying on as if nothing had happened, hiding real errors. - Using exceptions for normal flow, such as exiting a loop.
- Assuming that Python is slow at everything: what is slow is the hand-written loop; with NumPy the calculation runs in C.
08Self-assessment
What is encapsulation and what does it guarantee?
Keeping the data and the operations together, and exposing only what is necessary. It guarantees that the state changes only through validated paths.
What does polymorphism allow that would otherwise be costly?
Adding a new type without touching the code that uses it: the loop that traverses the list of instruments stays the same.
How does C++ resolve a call to a virtual method?
Through the virtual function table: the object stores a pointer to its class’s table and the call jumps to the corresponding entry, at run time.
Why is except: pass wrong?
Because it catches everything, including programming errors, and hides them. You should catch only the exceptions you know how to handle.
What does finally do?
It runs that block whether or not there is an exception: it is where the port, the file or the connection is closed.
Is it a good idea to use objects on a microcontroller with 2 kB of RAM?
Simple classes, yes; exceptions and dynamic allocation, no. The tool is chosen for the problem, not out of habit.
09Further reading
- Bjarne Stroustrup. A Tour of C++. 3rd ed., Addison-Wesley, 2022. Modern C++ in two hundred pages, written by its creator.
- Mark Lutz. Learning Python. 5th ed., O'Reilly, 2013. Thorough and careful about the language’s object model.
- Erich Gamma et al. Design Patterns. Addison-Wesley, 1994. The classic patterns. It is best read after having suffered the problems they solve.
- Robert C. Martin. Clean Code. Prentice Hall, 2008. Debatable on several points, but it installs the right question: will this still be understood a year from now.