I use links. This allows the developer to make and abstract the choice without risking the client (in some cases this is important, and some will not).
I also use them for a consistent style - I donβt like viewing public interfaces, which are passed on the details of their implementation.
Transients and copies can be expensive - they vary greatly in the type that you pass. Return by value means that the type must be trivially constructive, with the possibility of replacement, with the ability to copy, move. The compiler can do some great optimizations in this area (RVO / move), but you can also make informed decisions to minimize the costly operations in your implementations. When you no longer use types, everyone knows the characteristics of copying, and choosing the return method becomes very difficult, so I just keep it simple and prefer links.
Passing a link has several other advantages, for example, when the client prefers to use a subclass of the passed type.
Another advantage if you need an optimized program: I often delete a copy of ctor and operator= if they are not trivial or possible. Passing using a mutable reference allows you to work with types that are not copied / assigned.
In the strict sense of std::string , used in this question: returning the value of std::string by value is quite common, and for this case, many optimizations have been made - RVO, COW and moves - some well-known. As Voo points out in the comments below, returning by value is often easier to read. In the case of std::string and higher level programs, returning by value is unlikely to be a problem, but it is important to measure it in order to understand the costs associated with standard library implementations that you use if performance is important (which may be the case).
An important consideration is that if you are trying to improve an existing program, make sure that you understand how the implementation is performed and find out how to use types most efficiently when performance is important. Implementations can be written and optimized for actual use, which means that they can be pessimistic and in some cases guessing about you, and your attempts to improve performance may already be realized or unconventional use of this type may degrade performance. The typical resizing behavior of a std::vector is a good example. Using a high-performance road adds a lot of time and complexity to what you need to know in order to achieve the best results, and this obviously depends on the implementations you use, as well as the types you use. If productivity is critical and worth the non-trivial investment of time, learning about the types that you use is worthwhile, which can lead to significant success.
I should also add that I often work at low levels - where productivity is critical and / or resources are limited. There can be many restrictions, including exceptions, no locks (heap allocation is also not implied), minimal abstraction costs, and even limited use of dynamic polymorphism. It can be considered quite demanding, even for C ++. I choose the link for the main low-level components, but I will relax this rule if I know that the program will be used only in higher-level domains or unit tests.