Logo
Section 1 / 7

返回类型

让我们来学习函数可以返回的变量类型。

导入

返回类型

让我们来学习函数可以返回的变量类型。

函数可以返回值、引用和指针,而这些都可以是 const。但将它们转换为 const 是否有意义呢?让我们来探讨一下!

通过值返回 const 对象

初次接触 const 的开发者可能会对将所有内容都设为 const 充满热情,甚至尝试将 std::string getName() 这样的函数签名改为 const std::string getName() const。然而,将所有返回值都设为 const 并不是一个好主意。

为什么?

const 限定符向读者和编译器表明某个对象不应被修改。当我们通过值返回一个对象时,会为调用者创建一个副本。

让这个副本也变成 const 有意义吗?

想象一下,你买了一套房子,却不能对它做任何改动。在大多数情况下,你希望自己的房子是你的城堡。这就像我们副本与原对象之间的关系:我们希望对副本进行任意操作。

通过值返回的 const 对象是没有意义的。这不仅容易引起误解,甚至可能带来负面影响。

非 const 返回类型支持移动

对于非 const 返回类型,移动操作是可行的。假设我们有如下代码:

1#include <iostream> 2#include <algorithm> 3#include <vector> 4 5class SgWithMove { 6public: 7 SgWithMove() { 8 std::cout << "default constructor\n"; 9 } 10 11 SgWithMove(const SgWithMove& other) { 12 std::cout << "copy constructor\n"; 13 } 14 15 SgWithMove(SgWithMove&& other) noexcept { 16 std::cout << "move constructor\n"; 17 } 18 19 SgWithMove& operator=(const SgWithMove& other) { 20 std::cout << "copy assignment\n"; 21 return *this; 22 } 23 24 SgWithMove& operator=(SgWithMove&& other) noexcept { 25 std::cout << "move assignment\n"; 26 return *this; 27 } 28}; 29 30SgWithMove foo() { 31 SgWithMove ret; 32 // ... 33 return ret; 34} 35 36int main() { 37 SgWithMove o; 38 o = foo(); 39}

通过查看日志或使用调试器,我们可以发现 RVO(返回值优化)被成功应用,当 foo() 的返回值赋给 o 时,实际发生的是移动操作。

const 返回类型无法移动

现在我们给返回类型加上 const。

const 返回类型不再支持移动!

1#include <iostream> 2#include <algorithm> 3#include <vector> 4 5// 以下类定义未改变 6class SgWithMove { 7public: 8 SgWithMove() { 9 std::cout << "default constructor\n"; 10 } 11 12 SgWithMove(const SgWithMove& other) { 13 std::cout << "copy constructor\n"; 14 } 15 16 SgWithMove(SgWithMove&& other) noexcept { 17 std::cout << "move constructor\n"; 18 } 19 20 SgWithMove& operator=(const SgWithMove& other) { 21 std::cout << "copy assignment\n"; 22 return *this; 23 } 24 25 SgWithMove& operator=(SgWithMove&& other) noexcept { 26 std::cout << "move assignment\n"; 27 return *this; 28 } 29}; 30 31// 返回类型增加了 const 32const SgWithMove foo() { 33 SgWithMove ret; 34 // ... 35 return ret; 36} 37 38int main() { 39 SgWithMove o; 40 o = foo(); 41}

观察输出或使用调试器,我们发现并未发生移动赋值。相反,我们多做了一次拷贝。

因为我们返回的是 const SgWithMove,无法将其作为 SgWithMove&& 传递,因为这会丢弃 const 限定符(而移动操作本身会修改被移动的对象)。因此,调用的是拷贝赋值操作(const SgWithMove&),结果又生成了一个副本。

注意:许多书籍曾建议在返回用户自定义类型时使用 const 值。然而,由于近年来 C++ 标准的改进,这一建议已经过时了。

函数

返回常量引用