8.7.3 The return statement

1
A function returns to its caller by the return statement.
2

返回语句中的表达式或花括号初始化列表(expr-or-braced-init-list)称为它的操作数。

  • 不带操作数的返回语句只能用于①返回类型为cv void的函数、②构造函数、③析构函数中。
  • 操作数类型为void的返回语句只能用于返回类型为cv void的函数中。
  • 带有任何其他类型操作数的返回语句只能用于返回类型非cv void的函数中;
  • 返回语句通过从操作数拷贝初始化,来初始化当前(显式或隐式的)函数调用的返回对象(可能是泛左值或纯右值)。

【注释:如果操作数不是纯右值,或者其类型与函数的返回类型不同,则返回语句可能会调用构造函数来执行操作数的复制或移动(以构造要返回的对象)。如果返回的是具有自动存储周期的变量,则与返回语句关联的拷贝操作可以被省略或转换为移动操作([class.copy.elision])。 ——注释结束】

【示例:

1
2
3
4
std::pair<std::string,int> f(const char* p, int x)
{
return {p,x};
}

——示例结束】

返回对象的析构函数被潜在调用(potentially invoked)([class.dtor][except.ctor]
【示例:

1
2
3
4
5
class A
{
~A() {}
};
A f() { return A(); } // error: destructor of A is private (even though it is never invoked)

——示例结束】

对于构造函数、析构函数或返回类型为cv void的非协程函数(这些函数(可以)没有显式返回语句),执行到函数末尾(Flowing off the end)等价于一个没有操作数的返回语句。除此之外,如果控制流流出除main函数或协程([dcl.fct.def.coroutine])以外的其它函数(即缺少返回语句),将导致未定义行为。

Paw5zx个人理解:点击查看更多

返回语句通过从操作数拷贝初始化,来初始化当前(显式或隐式的)函数调用的返回对象(可能是泛左值或纯右值)。

这里描述的是RVO(返回对象是纯右值)和NRVO(返回对象是泛左值)机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct T
{
int id;
T(int i) : id(i) { std::cout << "T(" << id << ") ctor" << std::endl; }
T(const T& other) : id(other.id) { std::cout << "T(" << id << ") copy ctor" << std::endl; }
T(T&& other) noexcept : id(other.id) { std::cout << "T(" << id << ") move ctor" << std::endl; }
~T() { std::cout << "T(" << id << ") dtor" << std::endl; }
};

// RVO
T make_rvo() {return T(3); }
// NRVO
T make_nrvo()
{
T local(1); // 自动存储周期,非volatile,与函数返回类型相同
std::cout << "return local" << std::endl;
return local; // 表达式是对象名称
}

对于RVO,C++17已经强制要求,直接在返回对象上构造

如果操作数不是纯右值,或者其类型与函数的返回类型不同,则返回语句可能会调用构造函数来执行操作数的复制或移动(以构造要返回的对象)。如果返回的是具有自动存储周期的变量,则与返回语句关联的拷贝操作可以被省略或转换为移动操作。

1
2
3
4
5
6
7
8
9
10
11
12


// T结构体定义见前一示例

int main()
{
std::cout << "Calling make_nrvo()" << std::endl;
T a = make_nrvo();
std::cout << "make_nrvo() done" << std::endl;

return 0;
}

不考虑NRVO,在make_nrvo中,需要用local初始化返回对象,调用了移动构造函数。NRVO开启后会优化这一步。

3

函数调用结果的拷贝初始化,先序于由返回语句操作数所确立的完整表达式([intro.execution])末尾的临时对象的析构。而后者又先序于包含该返回语句的块内的局部变量([stmt.jump])的析构。

Paw5zx个人理解:点击查看更多

不用上述示例因为local是一个id-expression而不是full-expression