7.5 Dealing with Return Values

7.5 处理返回值

 

For return values, you can also decide between returning by value or by reference. However, returning references is potentially a source of trouble, because you refer to something that is out of your control. There are a few cases where returning references is common programming practice:

函数的返回值也可以按值或按引用返回。但是如果按引用返回,可能是一些潜在问题的根源。因为你引用的对象可能是一个己经失去控制的东西。在以下几种情况中,返回引用是一种常见的编程经验:

  • Returning elements of containers or strings (e.g., by operator[] or front())

  返回容器或字符串中的元素(如,通过[ ]运算符或front()函数)

  • Granting write access to class members

  允许修改类对象的成员

  • Returning objects for chained calls (operator<< and operator>> for streams and operator= for class objects in general)

  为链式调用返回一个对象(例如流操作中的<<或>>运算符,以及赋值运算符)

In addition, it is common to grant read access to members by returning const references.

此外,通常通过返回const引用,来授权对类成员的只读访问权。

Note that all these cases may cause trouble if used improperly. For example:

但是如果使用不当,以上几种情况就可以引发一些问题,例如:

std::string* s = new std::string("whatever");
auto& c = (*s)[0];
delete s;
std::cout << c; //ERROR:运行期错误

Here, we obtained a reference to an element of a string, but by the time we use that reference, the underlying string no longer exists (i.e., we created a dangling reference), and we have undefined behavior. This example is somewhat contrived (the experienced programmer is likely to notice the problem right away), but things easily become less obvious. For example:

这里声明了一个指向字符串中元素的引用。但是当我们使用该引用时,其底层字符串己经不存在了(即,我们创建了一个“悬挂引用”),这会导致未定义行为。这个例子有些人为设计(有经验的程序员可能马上就会注意到这个问题),但事情很容易变得不那么明显。例如:

auto s = std::make_shared<std::string>("whatever");
auto& c = (*s)[0];
s.reset();
std::cout << c; //run-time ERROR

We should therefore ensure that function templates return their result by value.

因此,我们应该确保函数模板按值返回结果。

 

However, as discussed in this chapter, using a template parameter T is no guarantee that it is not a reference, because T might sometimes implicitly be deduced as a reference:

但是,正如本章所讨论的那样,使用模板参数T作为返回类型并不能保证不会返回引用,因为T在某些情况下会被隐式的推导为引用

template<typename T>
T retR(T&& p) // p is a forwarding reference
{
    return T{…}; // OOPS: 当传入左值时,返回T&类型。    
}

Even when T is a template parameter deduced from a call-by-value call, it might become a reference type when explicitly specifying the template parameter to be a reference:

就算在模板中,T被声明为按值传递,也可以显式地将T指定为引用类型:

template<typename T>
T retV(T p) //Note: T might become a reference
{
    return T{…}; // OOPS: returns a reference if T is a reference
}

int x;
retV<int&>(x); // retT() instantiated for T as int&

To be safe, you have two options:

安全起见,你有两种选择:

  • Use the type trait std::remove_reference<> (see Section D.4 on page 729) to convert type T to a nonreference:

  使用std::remove_reference<>(见第729页的D.4节)来将类型T转换为非引用类型:

template<typename T>
typename std::remove_reference<T>::type retV(T p)
{
    return typename std::remove_reference_t<T>{…}; // always returns by value
}

  Other traits, such as std::decay<> (see Section D.4 on page 731), may also be useful here because they also implicitly remove references.

  当然也可以用其他类型萃取,如std::decay<>可能也有用(见第731页的D.4节),因为它们隐式地将引用移除。

  • Let the compiler deduce the return type by just declaring the return type to be auto (since C++14; see Section 1.3.2 on page 11), because auto always decays:

  将返回类型声明为auto,让编译器去推断返回类型。这是因为auto会导致类型退化(从C++14开始,请参阅第11页的1.3.2)。

template<typename T>
auto retV(T p) // by-value return type deduced by compiler
{
    return T{…}; // 总是按值返回
}