There are 2 objects in main: A a,b;, one object in the body of function f() : A b; and then there is temporary object that is being copied and its copy stored into b.
When returning b in the body of your function, copy is created at first, then the local b is destructed, then copy is assigned into variable b declared in main and then this copy is destructed.
Add following line to class A definition and see yourself:
A(const A&) { cout << "copying" << endl; }
With Named Return Value Optimization, the compiler tries to eliminate redundant Copy constructor and Destructor calls which means that local b from the function f() will be assigned into variable b in main without copy being created. So with RVO / NRVO only 3 objects are created in your case.
Although there is a way how to avoid destructing this copy without RVO in your case:
A a;
A b = a.f();
in this case copy of return value of function f() is created and stored as a variable b. Which also means that no assigment operator is called and only 2 objects are created in main: a and copy of b returned by f().
Hope this helps.