本文用于初步探索C++17
引入的std::any
的使用。
Verification Case
知识补充
c++
是静态类型的语言,python
是动态类型的语言。从个人经历而言,很多时候,python
一个函数或一个容器可以搞定的问题,在c++
中可能会写许多相似度高的代码、或泛化设计、或使用多态特性,或许性能上比较占优势,但实现复杂度相对高些。
any
容器可以当做一个阉割版的动态类型的设计,应用方式如下:
Put int/float/sturct...
to std::any
container and algorithm
Get from std::any
container and algorithm, identify and cast to int/float/sturct...
最简单粗暴的实现模式可能是:
1
2
3
4
5
6
7
8
9
10
| class any {
public:
virtual ~any() = default;
};
template <typename T>
class concrete_any : public any {
private:
T data;
};
|
若作一些内存上优化,进一步的实现模式可能是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| class any {
public:
template <typename T, template... Args>
any(Args&&... args) {
if (sizeof(T) <= sizeof(void*))
data.mem = std::get<0>(args);
else
data.ptr = (void*)new T(args...);
}
union member_type {
uint64_t mem,
void* ptr
};
private:
member_type data;
};
|
当然,此篇非做源码剖视,此处是为了看清楚any
这个语法糖实现动态类型的可能实现。
具体的定义方式颇为简单:
1
2
3
4
| std::any i = 1;
std::any f = 1.0f;
std::any s = std::string("hello");
std::vector<std::any> v = {1, 2.0f, std::string("hello")};
|
使用倒是比较啰嗦,需要借助any_cast
:
1
2
3
4
5
6
| auto iDecay = std::any_cast<int>(i);
auto fDecay = std::any_cast<float>(i);
auto sDecay = std::any_cast<std::string>(i);
auto v0Decay = std::any_cast<int>(v[0]);
auto v1Decay = std::any_cast<float>(v[1]);
auto v2Decay = std::any_cast<std::string>(v[2]);
|
std::any
接口比较少,进一步的了解可转cppreference[1].
应用场景
这里先引用stackoverflow
中的某个回答[3]:
- In Libraries - when a library type has to hold or pass anything without knowing the set of available types.
- Parsing files - if you really cannot specify what are the supported types.
- Message passing.
- Bindings with a scripting language.
- Implementing an interpreter for a scripting language
- User Interface - controls might hold anything
- Entities in an editor (ref)
从个人的角度,any
适用的场景或许是高度抽象的类型无关对象管理模块、类型无关代理模块,通过业务层面的约定而非传统的c/c++
的静态约束的方式来达成业务。
Scenario
就拿gcc9.3 -std=c++17 -O2 -o filename
这种简单的命令行来设计一个CommandLine
,用于做命令的注册和解析:
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
| #include <any>
#include <map>
#include <string>
#include <gtest/gtest.h>
class CommandLine {
public:
// Singleton should not be copyed and moved.
CommandLine(const CommandLine&) = delete;
CommandLine(CommandLine&&) = delete;
CommandLine &operator=(const CommandLine&) = delete;
CommandLine &operator=(CommandLine&&) = delete;
// Add command key-value
void addCommand(const std::string& name, std::any option) {
commands[name] = option;
}
// Add command exists
void addCommand(const std::string& option) {
commands[option] = std::any(true);
}
// Get option with default value
template <typename T>
T get(const std::string& key, T&& defRet) {
auto it = commands.find(key);
if (it == commands.end())
return defRet;
return std::any_cast<T>(it->second);
}
// Mainly for the boolean option
bool any(const std::string& key) {
auto it = commands.find(key);
if (it == commands.end())
return false;
return std::any_cast<bool>(it->second);
}
static CommandLine& ins() {
static CommandLine cmd;
return cmd;
}
private:
CommandLine() = default;
private:
std::map<std::string, std::any> commands;
};
// For enum case
enum class CPPVersion {
kCPP98,
kCPP11,
kCPP14,
kCPP17
};
TEST(CommandLine, test) {
auto& cmdl = CommandLine::ins();
// enum case
cmdl.addCommand("std", CPPVersion::kCPP17);
// bool case
cmdl.addCommand("O2");
// string case(or struct case)
cmdl.addCommand("o", std::string("filename"));
ASSERT_TRUE(cmdl.any("O2"));
ASSERT_FALSE(cmdl.any("O0"));
ASSERT_EQ(cmdl.get<CPPVersion>("std", CPPVersion::kCPP98), CPPVersion::kCPP17);
ASSERT_EQ(cmdl.get<std::string>("o", "a.out"), "filename");
}
|
这个命令行的例子,CommandLine
仅有一个map
成员,各种option
的类型和定义由各个命令自身的业务约束。无论是扩展命令类型或修改,CommandLine
基本无需任何适配。尤其是非通用的option
可以进一步限定在有限范围内定义和使用。
参考资料
[1] std::any
. https://zh.cppreference.com/w/cpp/utility/any
[2] std::any: How, when, and why. https://devblogs.microsoft.com/cppblog/stdany-how-when-and-why/
[3] When should I use std::any. https://stackoverflow.com/questions/52715219/when-should-i-use-stdany