I don't think I would fully agree with that. Exactly what does it matter
what kind of assembler opcodes the compiler is generating from your code?
C++ is a C language, which is a kind of "portable assembler".
The point of using C++ is writing high-level code that compiles very
tight and runs very fast. If you don't need small footprint and high
speed, then use a language such as Ruby, which is gentler to the
programmer, and harsher to the CPU.
In fact, I would say the exact opposite: You should not worry which
opcodes the compiler is generating, mainly because the compiler will
often generate wildly different opcodes for the same C++ code depending
on the compiler itself, the target architecture and the amount of
optimizations. The exact opcodes being generated is rather irrelevant
from the point of view of programming in C++.
Most of the time you do not worry about the specific opcodes.
C++ is defined in terms of a virtual architecture. That's not poetry,
it's how the Standards work. C++ calls a function by pushing arguments
onto a stack or into registers, then pushing its own address onto the
stack, then jumping into another address. That address is expected to
pop the arguments off the stack and use them, then pop the return
address off and jump back to it.
You don't think of any of that when you code. And someone could port C+
+ to an architecture, such as DNA or photonics, which does not have a
stack, or pushes, or pops, etc.
A syntax error at compile time represents an inability to create valid
opcodes, such as codes that align data types and copy them around.
Disregard that and fix the syntax error.
And the language allows many dodges, from unions to reinterpret_cast,
to allow you to turn off that syntax, and enforce the behavior that
you want. For example, indexing off the end of a minor dimension of a
multidimensional array is well-defined, if you are still within the
major dimensions.
And when you make a mistake and have to diagnose a crash,
understanding all the ways you can stomp the CPU sure helps! C++ gets
as close as possible to the Von Neumann Architecture, of pointers,
registers, accumulators, opcodes, and stacks, while remaining
portable. This also explains the 1001 "undefined behaviors" it can
generate.
That's not to say that *everything* that is extremely low-level is
completely irrelevant. For example, there may be situations where memory
locality and cache optimization may affect the efficiency of the program
very significantly (eg. your program could become an order of magnitude
faster if you do something more cache-optimally). However, this has nothing
to do with exactly what assembler opcodes the compiler is generating.
The point of using C++ is writing fast, tight code via some awareness
what the operations cost. Learning assembler (even for another
architecture) helps that, even when you only keep it in the back of
your mind.