|
在诸如python,js等脚本语言中,其变量都是动态类型的。
a = 3
a = 3.14
a = "hello wrold"在C/C++中,如何实现一个类似的功能呢?
Any x = 3;
cout << x << endl;
x = 3.14;
cout << x << endl;
x = string(&#34;hello wrold&#34;);
cout << x << endl;
首先,在C语言中,很多库都是使用 void * 来实现动态类型的。
因为void * 可以指向任意类型。
struct A{
};
struct B {
};
int main() {
void* p = new A();
p = new B();
p = new vector<int>();
}
同样的,还有父类型的指针可以指向子类型
struct A{
};
struct B : A{
};
struct C : A {
};
int main() {
A* p = new B();
p = new C();
}
当然在一些限定了类型种类的语言解析器中。会使用union加一个type 来实现动态类型。

有关union 的文章 严格鸽:C/C++ union 使用教程 (常见操作与缺陷)
这样的做法实际上更类似std::variant 这里,我们准备利用父类指针可以指向子类来实现一个std::any
我们一步步的来,首先是父类与子类
struct Base {
};
template<typename T>
struct Data : Base{
T data;
Data(const T& t) : data(t) {}
};
int main() {
Base* p;
p = new Data<string>(&#34;hello world&#34;);
p = new Data<int>(3);
}
通过模板,生成不同的Data子类。
不过每次都要写Data<type>不是太美观,所以用一个类包一下吧
struct Any {
template<typename T>
Any(const T& t) : ptr{ new Data<T>(t) } {};
struct Base {
};
template<typename T>
struct Data : Base {
T data;
Data(const T& t) : data(t) {}
};
Base *ptr;
};
struct A{
int x, y;
};
int main() {
Any x = 2;
x = 3.141;
x = A{ 3,5 };
}
emmmm,感觉不是,但是是否发现了一个问题。
我们访问不了数据啊。。。
毕竟这个Any里面只有一个Base的指针,而真正的内容在子类里面。所以呢?我们需要加上一个强制类型转换来获得子类的数据。
struct Any {
template<typename T>
Any(const T& t) : ptr{ new Data<T>(t) } {};
struct Base {
};
template<typename T>
struct Data : Base {
T data;
Data(const T& t) : data(t) {}
};
Base *ptr;
template<typename T>
T& get_data() {
return ((Data<T>*)ptr)->data;
}
};
struct A{
int x, y;
};
int main() {
Any x = 2;
x = 3.141;
cout << x.get_data<double>() << endl;
x = A{ 3,5 };
cout << x.get_data<A>().x << &#34; &#34; << x.get_data<A>().y << endl;
}
感觉不错?
考虑如何进行拷贝。
对于这样的代码
Any x = 114514;
Any y = x;
我们希望y是复制了x的内容。
Any x = 114514;
Any y = move(x);
如果是move的话,我们希望y直接获得x原本的内容。
struct Any {
template<typename T>
Any(const T& t) : ptr{ new Data<T>(t) } {};
Any(Any&& rhs) noexcept {
ptr = rhs.ptr;
rhs.ptr = 0;
}
Any& operator=(Any&& rhs) noexcept {
if (ptr)delete ptr;
ptr = rhs.ptr;
rhs.ptr = 0;
return *this;
}
struct Base {
};
template<typename T>
struct Data : Base {
T data;
Data(const T& t) : data(t) {}
};
Base *ptr;
template<typename T>
T& get_data() {
return ((Data<T>*)ptr)->data;
}
};
int main() {
Any x = 114;
Any y = move(x);
Any z = 514.1919;
cout << y.get_data<int>() << endl;
y = move(z);
cout << y.get_data<double>() << endl;
}
挺好的,那么如何复制呢?当然,我们也可以显式的给出类型,然后new一个出来,但是这样不够优雅。
所以我们需要引入运行期消耗,也就是虚函数了。
struct Base {
virtual Base* clone() = 0;
};
template<typename T>
struct Data : Base {
T data;
virtual Base* clone() { return new Data<T>(data); }
Data(const T& t) : data(t) {}
};
这样通过clone就可以new了。
PS:动态类型基本上都需要引入运行期的消耗 修改一下Any,并加上析构函数
struct Any {
Any() : ptr{ 0 } {}
template<typename T>
Any(const T& t) : ptr{ new Data<T>(t) } {};
Any(const Any& rhs) {
ptr = rhs.ptr->clone();
}
Any& operator=(const Any& rhs) {
if (ptr)delete ptr;
ptr = rhs.ptr->clone();
return *this;
}
Any(Any&& rhs) noexcept {
ptr = rhs.ptr;
rhs.ptr = 0;
}
Any& operator=(Any&& rhs) noexcept {
if (ptr)delete ptr;
ptr = rhs.ptr;
rhs.ptr = 0;
return *this;
}
struct Base {
virtual Base * clone() = 0;
};
template<typename T>
struct Data : Base {
T data;
virtual Base* clone() { return new Data<T>(data); }
Data(const T& t) : data(t) {}
};
Base *ptr;
template<typename T>
T& get_data() {
return ((Data<T>*)ptr)->data;
}
~Any(){
if (ptr)delete ptr;
}
};
int main() {
Any x = 114;
Any y = x;
y.get_data<int>() = 514;
cout << x.get_data<int>() << endl;
cout << y.get_data<int>() << endl;
}
看上去已经不错了,但是,这个输出有点麻烦了,所以我们重载一下输出。
依然已经有虚函数了,那再加一个也无所谓了。
struct Base {
virtual Base* clone() = 0;
virtual ostream& print(ostream& out) = 0;
};
template<typename T>
struct Data : Base {
T data;
Data(const T& t) : data(t) {}
virtual Base* clone() { return new Data<T>(data); }
virtual ostream& print(ostream& out) {
out << data;
return out;
}
};
ostream& operator << (ostream& out, const Any& oth) {
oth.ptr->print(out);
return out;
}
这样就能实现直接输出了。
Any能不能和stl进行嵌套呢?
这里重载一下vector的输出
template<typename T>
ostream& operator << (ostream& out, const vector<T>& v) {
out << &#34;[&#34;;
for (auto it = v.begin(); it != v.end(); it++) {
if (it != v.begin())out << &#34;,&#34;;
out << *it;
}
out << &#34;]&#34;;
return out;
}
这样来个vector与Any的嵌套
int main() {
vector<Any> vec = { 114,514,1919,8.10,string(&#34;asoul&#34;),string(&#34;4soul&#34;) };
cout << vec << endl;
vector<Any>oth = { string(&#34;ygg&#34;),233 };
vec.push_back(oth);
cout << vec << endl;
vec[1] = vector<Any>{ oth,oth };
cout << vec << endl;
}
输出
[114,514,1919,8.1,asoul,4soul]
[114,514,1919,8.1,asoul,4soul,[ygg,233]]
[114,[[ygg,233],[ygg,233]],1919,8.1,asoul,4soul,[ygg,233]]
有一点需要大家注意

这个地方,如果你使用data{} 来初始话,在msvc 和 gcc 编译器上大概率会报错,但是clang不会...
具体原因之前问过一些群友,好像是 T = vector data{} 匹配到initializer list 上去了。
另外可能有人觉得,你这个输出的内容好像Json啊,是不是可以用这个来写Json的parser呢?
可以是可以,但对于这个确定了类型的,我们还有variant

还有这个Any类可以优化的,毕竟一个指针加一个虚表就16字节了。

可以在编译器确定类的大小,小的对象就不要new了,当然这可能需要一些模板元编程的知识了。
严格鸽:现代C++学习 模板元编程入门
code
struct Any {
Any() : ptr{ 0 } {}
template<typename T>
Any(const T& t) : ptr{ new Data<T>(t) } {};
Any(const Any& rhs) {
ptr = rhs.ptr->clone();
}
Any& operator=(const Any& rhs) {
if (ptr)delete ptr;
ptr = rhs.ptr->clone();
return *this;
}
Any(Any&& rhs) noexcept {
ptr = rhs.ptr;
rhs.ptr = 0;
}
Any& operator=(Any&& rhs) noexcept {
if (ptr)delete ptr;
ptr = rhs.ptr;
rhs.ptr = 0;
return *this;
}
struct Base {
virtual Base * clone() = 0;
virtual ostream& print(ostream& out) = 0;
};
template<typename T>
struct Data : Base {
T data;
Data(const T& t) : data( t ) {}
virtual Base* clone() { return new Data<T>(data); }
virtual ostream& print(ostream& out) {
out << data;
return out;
}
};
Base *ptr;
template<typename T>
T& get_data() {
return ((Data<T>*)ptr)->data;
}
~Any(){
if (ptr)delete ptr;
}
};
ostream& operator << (ostream& out, const Any& oth) {
oth.ptr->print(out);
return out;
}
template<typename T>
ostream& operator << (ostream& out, const vector<T>& v) {
out << &#34;[&#34;;
for (auto it = v.begin(); it != v.end(); it++) {
if (it != v.begin())out << &#34;,&#34;;
out << *it;
}
out << &#34;]&#34;;
return out;
}
int main() {
vector<Any> vec = { 114,514,1919,8.10,string(&#34;asoul&#34;),string(&#34;4soul&#34;) };
cout << vec << endl;
vector<Any>oth = { string(&#34;ygg&#34;),233 };
vec.push_back(oth);
cout << vec << endl;
vec[1] = vector<Any>{ oth,oth };
cout << vec << endl;
}
上一期内容严格鸽:现代C++学习——实现一个std::tuple
下一期就实现variant,与any不同,可以做到运行期基本无消耗(为维护一个type)。我们可以用variant来实现Json |
|