在尝试查找将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;
}
感觉非常狡猾,所以我试图寻找另一种选择。 This post提出了使用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风格演员有什么缺点?
感谢。
答案 0 :(得分:3)
为什么第一个片段甚至编译但使用static_cast会导致编译错误?
因为C型演员是一把大锤,会对风大加注意。它的座右铭是&#34;你想要它吗?你得到它&#34;,不管是什么&#34;它&#34;是。静态强制转换只会在静态类型检查方面进行正确的强制转换。
这两种方法的计算成本是多少?我希望第二个因为创建临时矢量对象new_vec而产生额外成本,但我不确定。
您的期望是正确的。但是具有明确定义的语义的代码的成本可以为程序增加工作量。
在这些情况下使用C风格演员有什么缺点?
它将始终编译,并且在您尝试在将来的某个平台上运行它之前,您不会发现问题。因为今天可能工作。
答案 1 :(得分:2)
那段代码是无稽之谈。不要求Derived*
的值与Base*
的值相同,因此告诉编译器假装std::vector<B*>
std::vector<A*>
对于任何明智的事情都不是必需的#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;
}
。实际上,如果您有多个相同类型的基数,那么指针类型双关语是不可能的。试试吧:
{{1}}
答案 2 :(得分:1)
auto x = (Foo) someConstType
,你的意思是删除const
限定词或者是偶然的?)。在您的特定情况下,如果您有多个继承,C样式版本将产生不正确的程序,并且向上转换指针意味着其地址需要更改为指向适当的基类对象。
答案 3 :(得分:1)
std::vector<Derived*>
是与std::vector<Base*>
无关的类型。没有合法的方法可以将一个人的记忆解释为另一个,而不是像放置新事物一样疯狂的愚蠢。
如果幸运的话,你的尝试会产生错误。如果你不是,它们会产生未定义的行为,这意味着它似乎可以在今天工作,但明天它们可以静默格式化你的硬盘驱动器,因为从编译器升级到代码远离改变,或月亮的阶段。 / p>
现在,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模式。