Random Thoughts on C++
Time flies so fast that I suddenly realized that I have spent almost a whole year on my Unreal-based robot simulator project, Pavilion.
In the process of implementing that concept, I have grown up as a half-professional Unreal developer-yet the project is still a big mess with tons of spaghetti-like code. This project finally made me realize that I am not omnipotent. I cannot single-handedly make a huge project work like magic in such a short time.
In retrospect, the current code base is non-maintainable to say the least. Functionalities are not clearly separated to modules, and modules have too much inter-dependencies that they have to be pulled together when you just need one, just like the famous-and-infamous Boost. Exceptions seems unavoidable. RTTI can only be eliminated by manual effort, or you will have to enable RTTI for the entire Unreal Engine.
RTTI
Who is to blame for this situation? RTTI is a controversial topic in C++ since its advent. It's sort of like GPL in the sense that it "infects" all the libraries that uses it, whether its shared (dynamically-linked) or static. The only way out is to expose all APIs in C, which then makes object-oriented programming a joke.
However, RTTI is not evil. It is indeed useful in many sense. It is just broken in implementation. However, many programmers actually misuse RTTI for cases where a virtual function would suffice. If that is not the case, LLVM-style RTTI would in 99.9% of the cases, which does not require the C++ RTTI system to be on. Sadly, most C++ programmers simply takes the short path and enable RTTI, without considering the consequences.
Exceptions
Exceptions are said to have "zero-cost" in modern C++. Yet we know that nothing in this world comes truly with no cost. Exceptions adds a lot of footprint to your binary. Misuse of exceptions creates alternate code paths that is implicit, that makes your codebase bug-prone. Throwing in constructors is also a highly controversial issue.
At least 50% of the use of exceptions in C++ can be replaced without using it. For example, a throw in constructor when resource acquisition can be replaced for a non-throwing constructor and a throw in subsequent calls. Or even returning a bool
in a init()
method.
class A {
public:
A() {
bool connected = false;
// connect to a server
if (!connected) {
throw NotConnectedException();
}
}
}
class B {
bool connected = false;
public:
B() {
// connect to a server
}
void send() {
if(!connected) {
throw NotConnectedException();
}
}
}
class C {
bool connected = false;
public:
C() {
}
bool init() {
// connect to a server
return connected;
}
}
As you can see in the above example, class C
entirely avoided the use of exceptions, while abide by the RAII (Resource-Acquisition-Is-Initialization) principle. Again, the problem is that most programmers write code that works, not code that works for everyone.