|I can't take the compiled binary and add any more functions to it externally (or can I?)|
You can. A dynamic library lets you adds arbitrary code to a program, as long as the program is designed to do this. For example, you can have a program that
1. Checks if a dynamic library is present.
2. If it is, attempts to load it and its functions.
3. If successful, calls a function in the library that returns an array of function pointers.
Step #3 lets you add as much behavior to the program as you want without having to recompile the program. Additionally, because the libraries are dynamically loaded, you can define in a configuration file library names for the program to search. This way you can even add as many libraries as you want (within reason) without recompiling unrelated libraries.
* Good performance because you're running native code directly, without any extra cost.
* Writing C/++ takes more time than other languages.
* Writing dynamic interfaces between C++ modules is tricky. Due to name mangling, you can't export C++ symbols in the dynamic library if you expect the library and the program to not necessarily be compiled by the same compiler and compiler version. You have to export C symbols. This leads to other restrictions.
* (I think) it's possible to implement dynamic reloading (that is, pausing execution, unloading the library, then reloading a new version of the library and resume execution as if nothing had happened), but it's complicated. Also known as "hot swapping".
This is one way of implementing a plug-in system.
Another is to use an interpreted language to define behavior at run time. The idea is pretty much the same, only instead of loading executable code, you load scripts that the program reads as it goes. If you want to use this, you can either create your own language, or use an existing language. If you choose the latter option, I suggest Lua. It's specifically designed to be embedded in C/++, and it has coroutines, which make defining state machines easy without going outside of the procedural paradigm.
* Scripting languages are typically easy to write in.
* There's no need to deal with binary compatibility issues because no binaries exist.
* Dynamic reloading is often explicitly supported by the language.
* Performance is always worse than with native code. In some cases, much worse (*cough*Python*cough*).