Class initialization
For a class with user-defined constructors like Foo, the only difference between the initializations is that the last (called copy-initialization) is equivalent to
Foo bar3 = Foo(5);
That is, the initializer expression is converted to the class type (via a constructor) and then bar3 is initialized from that. (Until C++17 this notionally involves a copy or move, but compilers routinely avoid that overhead.)
Note that there is one more syntax:
Foo bar4={5};
This is known as copy-list-initialization, but the only difference from the version without the = is that explicit constructors are disallowed.
Structural initialization
The lack of significant differences between these forms fails to motivate having them and is therefore confusing. The distinction comes with containers, broadly construed so as to include aggregates (simple C-like structs). Those support {}-initialization with a different meaning, which is to construct an object containing some data, rather than computed from it. So
std::vector<double> a(10,1),b{10,1};
defines a to have 10 values (which are each 1) and b to have the two values 10 and 1.
It has been suggested that use of braces to initialize should be restricted to this meaning to avoid confusion.
Primitive initialization
Finally, we have your int examples. It should be clear now that all the initializations are interchangeable (except that list-initialization prohibits narrowing conversions), since there is no distinction between computing a primitive value and filling it. Nor is there any possibility of observing the initialization of (or assignment to) a variable of primitive type: it simply comes to have the value supplied as an atomic action of the abstract machine. (Compilers know this and elide as many such variables as they can for efficiency.)
Objects
Even an int is an object (in the C++ sense of that word, not the Java or Python sense), and when you have a Foo you have two objects: the Foo itself and the int num it contains (which is created and destroyed along with it). So your initializations of Foo objects are also two initializations each: the outer one consisting of the inner one (which is always performed by the num(n) member-initializer).
It is the existence of this extra object that distinguishes Foo from int, without having to define a class operationally in terms of a memory layout: the operations allowed on a Foo are in general different because it is distinct from any object(s) it might contain.
-O0(no optimization) vs.-O2("significant" optimization). With the latter, the generated code forint n = a;was optimized away andFoo bar(b);was just a singlemovl- IMHO not much difference. (The I/O was introduced to have side-effects which prevent optimizing everything away.)Foonot possible (without overloading) orstd::is_integral.