Since you can't compete on price, then compete on all of the other selling points that the software has:
- features
- quality
- effectiveness
- integration with other software
- service
- support
- direct selling
Basically, you do what every other company does when they're in price competition: keep pace, or change the game.
When I have to return to a section of code I've not worked on for a
while, I have difficulty remembering how it worked. I spend a lot of
time reviewing the header files for the relevant classes and reading
the comments I placed along the way in the source files.
Imagine how the poor guy who comes after you is going to feel -- he doesn't even have the benefit of having once known how your code works. Instead of trying to decipher your code, you should be reviewing the documentation that you wrote for the module in question. That documentation should offer a reasonably accurate view of what the module does any why. It's not "the module starts by initializing three arrays using a triple for loop...", but instead: "this module retrieves the data collected by the Fabulotron's main sensor, rearranges it into standard Neopolitan (chocolate, vanilla, strawberry) format, and delivers it to the Analysis module."
In a perfect world, you'd have a design document that sets out the various modules in the system and describes their respective responsibilities, and each of your modules could just refer back to that document to explain what they do: "This module provides the Fabulotron data collection service as detailed in section 4.8 of the design document: http://fabulotron.org/design/section4-8.html." If you don't have something like that, start writing down an overview of each module as you work on it. You don't need to write a book -- a few paragraphs are often enough to get you oriented.
When I introduce changes, sometimes I realize half-way that what I'm
trying to do will break things somewhere else (or worse, it shows up
only at runtime as a surprise). I revert and start doing it
differently, only to find out I neglected the influence on some other
component.
That might be an indication that your modules are too interconnected. The more independent you can make your modules/classes/units, the less likely it will be that you'll run into this kind of problem. Try to make the interfaces between modules as explicit as possible, and try to limit them to just what needs to be there. An interface is a contract -- if you know that some module lives up to its obligations as specified in the interface, you don't need to know anything else about it. That prevents changes in the module you're working on from affecting other modules.
I wish there was some "architecture diagram" where I could see how things get done
Using standard parts can help in this respect. C++ provides standard parts in the form of the Standard Template Library, and using those parts where appropriate lets you work at a higher level of abstraction. If you've written your own code to manage data structures like lists, you and those that follow you will constantly have to read the source to figure out what's going on. If you instead use standard parts provided by the STL, any C++ programmer will quickly be able to tell what your code is doing without digging into your data management routines.
Another kind of standard part comes from design patterns. They're standard concepts that can be used as a shorthand to explain how the relationship between two objects works.
Best Answer
The assumption is invariably made that you know what you're doing and have a reasonably intimate understanding of what you're going (and expecting) to see.
If you look into the PHP code of the Symfony framework, for instance, you're expected to already know about dependency injection, events, the model/view/controller pattern, and so forth.
Likewise, if you dive into the C code of the linux kernel, the assumption is that you'll be realistically competent in modularity, signals, processes, threads and what not. You're also expected to have a knack to eat hexadecimal all day and excavate through core dumps with a giant shovel.
The maintainers won't go through the trouble of documenting the architecture because it's matter-of-factly stuff. On occasion, you'll find an outline of what lies where in the source tree. More typically though, the way the source tree is organized makes things self-explanatory.
In short, if you lack any of the skills that the maintainers will expect you know by the time you peek into their code, you're probably digging through stuff that is widely above your pay grade. Familiarize yourself with the concepts first - What is the MVC model? What is dependency injection? Etc. Then dive.