关于std::optional传递开销的讨论与优化
在讨论std::optional之前,我们应该先适当谈论一下“可空类型”。
我们知道,在传统的C++中,是不存在现代编程语言中常见的“可空类型”(如C#中的Nullable
1 | //C# |
显而易见,传统C++使用标记值来对某一变量设置为空的行为相比现代语言是存在问题、丑陋且不安全的:
- 某些情况下为了考虑标记值不得不放弃最为合适的数据类型(比如unsigned int之于size)
- 其他使用者容易忘记甚至不了解标记值的含义
- ……
std::optional
为了解决这一问题,C++17中引入了std::optional,实现了安全的可空值。
1
std::optional<unsigned int> size = std::nullopt; //通过std::nullopt将size设置为空
1
void Func(const std::string& str); //引用传递,不会发生拷贝,当Func不改变str时将其修饰为const
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//测试类
class Test
{
public:
std::string s = "Fuxk Cpp.";
Test()
{ std::cout << "\tctor\n"; }
~Test()
{ std::cout << "\tdtor\n"; }
Test(const Test&)
{ std::cout << "\tcopy ctor\n"; }
Test(Test&&) noexcept
{ std::cout << "\tmove ctor\n"; }
Test& operator=(const Test&)
{ std::cout << "\tcopy op= \n"; return *this; }
Test& operator=(Test&&) noexcept
{ std::cout << "\tmove op= \n"; return *this; }
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31//测试函数
void OptFunc(std::optional<Test> x)
{
std::cout << std::format("\t{}\n", x->s);
}
void DefaultFunc(const Test& x)
{
std::cout << std::format("\t{}\n", x.s);
}
//测试代码
int main()
{
Test t0;
std::cout << "-----------------------------------------\n";
std::cout << "Use Optional:\n";
std::cout << "copy:\n";
OptFunc(t0);
std::cout << "-------------\n";
std::cout << "move:\n";
OptFunc(Test());
std::cout << "-----------------------------------------\n";
std::cout << "Not use Optional:\n";
std::cout << "copy:\n";
DefaultFunc(t0);
std::cout << "-----------------\n";
std::cout << "move:\n";
DefaultFunc(Test());
std::cout << "-----------------------------------------\n";
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 ctor
-----------------------------------------
Use Optional:
copy:
copy ctor
Fuxk Cpp.
dtor
-------------
move:
ctor
move ctor
Fuxk Cpp.
dtor
dtor
-----------------------------------------
Not use Optional:
copy:
Fuxk Cpp.
-----------------
move:
ctor
Fuxk Cpp.
dtor
-----------------------------------------
dtor1
void OptFunc(const std::optional<Test>& x); //这样?
1
void OptFunc(std::optional<const Test&> x);
这样一来好像走到了死胡同。但C++还是给我们留了一个缺口,即std::reference_wrapper。
>
std::reference_wrapper是包装引用于可复制、可赋值对象的类模板。它常用作将引用存储入无法正常保有引用的标准容器(类似std::vector)的机制。
可用T类型的std::reference_wrapper的optional保有引用。
通过reference_wrapper,我们可以做到等同于std::optional<const
Test&>的效果,我们添加一个函数并编写新的测试代码: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//新添加测试函数
void OptWithRefWrapperFunc(std::optional<std::reference_wrapper<const Test>> x)
{
std::cout << std::format("\t{}\n", x->get().s);
}
//新添加测试代码
int main()
{
//...
std::cout << "Use Optional with std::reference_wrapper:\n";
std::cout << "copy:\n";
OptWithRefWrapperFunc(t0);
std::cout << "-----------------\n";
std::cout << "move:\n";
//FuncOptWithRefWrapper(Test());
std::cout << "-----------------------------------------\n";
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 ctor
-----------------------------------------
Use Optional:
copy:
copy ctor
Fuxk Cpp.
dotr
-------------
move:
cotr
move cotr
Fuxk Cpp.
dtor
dtor
-----------------------------------------
Not use Optional:
copy:
Fuxk Cpp.
-----------------
move:
ctor
Fuxk Cpp.
dtor
-----------------------------------------
Use Optional with std::reference_wrapper:
copy:
Fuxk Cpp.
-----------------
move:
-----------------------------------------
dtor1
2
3
4
5std::cout << std::format("{}, {}\n", sizeof(const Test*),
sizeof(std::optional<std::reference_wrapper<const Test>>));
//output:
//x86: 4, 8
//x64: 8, 16
Reference
https://abseil.io/tips/171 https://zh.cppreference.com/w/cpp/utility/optional https://stackoverflow.com/a/47842325/12822957 https://abseil.io/tips/163 https://zh.cppreference.com/w/cpp/utility/functional/reference_wrapper
原文链接: https://zhuanlan.zhihu.com/p/438821425