[英]C-style cast of std::vector
当试图找到将std::vector<Derived *>
转换为std::vector<Base *>
的解决方案时,我在现有的代码库中偶然发现了此实现。 我正在使用C ++ 11。
考虑以下代码片段:
#include <iostream>
#include <vector>
class A
{
// some implementation details
};
class B : public A
{
// some implementation details
};
void count(std::vector<A *> const & a_vec)
{
std::cout << "IT HAS THESE MANY PTRS: " << a_vec.size() << std::endl;
}
int main()
{
B * b;
std::vector<B *> b_vec {b};
count((std::vector<A *> &) b_vec);
return 0;
}
感觉很狡猾,所以我试图找到一个替代方案。 这篇文章提出了一种使用std::vector::assign
。 所以现在,我的主要功能如下所示:
int main()
{
B * b;
std::vector<B *> b_vec {b};
std::vector<A *> new_vec;
new_vec.assign(b_vec.begin(), b_vec.end());
count(new_vec);
return 0;
}
它可以按预期进行编译和工作。 现在我有以下问题:
1)为什么第一个代码片段甚至可以编译,但是使用static_cast
会导致编译错误?
2)两种方法的计算成本是多少? 由于创建临时矢量对象new_vec
,我预计第二个会产生额外的费用,但我不确定。
3)在这种情况下,使用C样式转换有哪些弊端?
谢谢。
为什么第一个代码段甚至可以编译,但是使用static_cast会导致编译错误?
因为C型投掷是一把大锤,将使一切警惕。 它的座右铭是“您想要它吗?您得到了它”,无论它是什么。 静态类型转换只能执行在静态类型检查方面正确的类型转换。
这两种方法的计算成本是多少? 由于创建临时矢量对象new_vec,我预计第二个会产生额外的费用,但是我不确定。
您的期望是正确的。 但是具有明确定义的语义的代码成本可能会增加程序的工作量。
在这些情况下,使用C样式转换有哪些弊端?
它会一直编译,并且直到将来尝试在某个平台上运行它时,您才发现存在问题。 因为它今天可能会工作。
该代码是胡说八道。 没有要求,即a的值 Derived*
是相同的a的值 Base*
,因此告诉编译器假装一个std::vector<B*>
是一个std::vector<A*>
是不要求对任何明智的事情。 实际上,如果您有多个相同类型的碱基,则该指针类型pun是不可能的。 试试吧:
#include <iostream>
struct Base {
int i;
};
struct I1 : Base {
int j;
};
struct I2 : Base {
int k;
};
struct Derived : I1, I2 {
int l;
};
int main() {
Derived d;
Base* b1 = &(I1&)d;
Base* b2 = &(I2&)d;
std::cout << (void*)&d << ' ' << (void*)b1 << ' ' << (void*)b2 << '\n';
return 0;
}
auto x = (Foo) someConstType
,您是要删除const
限定词还是那是偶然吗?)。 在您的特定情况下,如果您具有多重继承,则C样式的版本将产生不正确的程序,并且向上转换指针意味着需要更改其地址以指向适当的基类对象。
std::vector<Derived*>
是与std::vector<Base*>
不相关的类型。 没有合法的方法可以将一个人的记忆解释为另一个人的记忆,只是缺少像新安置这样的疯狂愚蠢的东西。
如果幸运的话,您的尝试会产生错误。 如果不是这样,它们会产生不确定的行为,这意味着它似乎在今天可以正常工作,但是明天,由于从编译器升级,更远的代码更改或月球阶段的各种变化,它们可以以无提示的方式格式化硬盘。
现在,情况是在vector<Base*>
上进行的许多操作都在vector<Derived*>
。 我们可以用类型擦除来解决这个问题。
这是一个低效率的类型擦除类:
template<class R, class...Args>
using vcfunc = std::function<R(void const*, Args...)>;
template<class T, class R, class...Args, class F>
vcfunc<R,Args...> vcimpl( F&& f ) {
return [f=std::forward<F>(f)](void const* pt, Args&&...args)->R{
return f( *static_cast<T const*>(pt), std::forward<Args>(args)... );
};
}
template<class T>
struct random_access_container_view {
using self=random_access_container_view;
struct vtable_t {
vcfunc<std::size_t> size;
vcfunc<bool> empty;
vcfunc<T, std::size_t> get;
};
vtable_t vtable;
void const* ptr = 0;
template<class C,
class dC=std::decay_t<C>,
std::enable_if_t<!std::is_same<dC, self>{}, int> =0
>
random_access_container_view( C&& c ):
vtable{
vcimpl<dC, std::size_t>( [](auto& c){ return c.size(); } ),
vcimpl<dC, bool>( [](auto& c){ return c.empty(); } ),
vcimpl<dC, T, std::size_t>( [](auto& c, std::size_t i){ return c[i]; } )
},
ptr( std::addressof(c) )
{}
std::size_t size() const { return vtable.size( ptr ); }
bool empty() const { return vtable.empty( ptr ); }
T operator[](std::size_t i) const { return vtable.get( ptr, i ); }
};
现在这有点玩具,因为它不支持迭代。 (迭代器大约和我上面写的容器一样复杂)。
现场例子 。
struct A {
char name='A';
};
struct B:A {
B(){ name='B'; }
};
void print_them( random_access_container_view<A> container ) {
for (std::size_t i = 0; i < container.size(); ++i ) {
std::cout << container[i].name << "\n";
}
}
int main() {
std::vector<B> bs(10);
print_them( bs );
}
允许将子容器视为基本列表的语言基本上会自动执行上述操作。 容器本身具有与虚拟功能表等效的功能,或者当您将容器视为基于虚拟功能表的视图时,则由客户端代码合成和使用。
上面的代码效率不是最高; 请注意,每个std::function
都是无状态的。 我可以很轻松地用函数指针替换它们,并基于C
类型存储一个静态vtable来节省内存(但添加另一个间接寻址)。
我们也可以使用非视图类型来简化此操作,因为我们可以使用类型擦除模型概念模式代替此手动vtable模式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.