使用 *void 作为 static_cast 的缓冲区

Posted

技术标签:

【中文标题】使用 *void 作为 static_cast 的缓冲区【英文标题】:using *void as a buffer for static_cast 【发布时间】:2012-08-06 15:37:25 【问题描述】:

所以我去:

class A;

class B : public A;

class C : public B;

vector<A*> *vecA;

vector<C*> *vecC;

我想将一个vectC转换成一个vecA。

vector<A*> *_newA = static_cast< vector<A*>* >(vecC); //gives an error

所以我使用 void 指针作为缓冲区和强制转换:

void *buffer = vecC;

vector<A*> *_newA = static_cast< vector<A*>* >(buffer); //works

这有效吗?还有其他方法吗?

【问题讨论】:

指向std::vector 的指针,这是错误的!不要这样做! 另外,你应该投射你放入向量中的东西,而不是向量本身。 几乎没有理由动态分配向量。 我建议你get yourself a good C++ book。 new 是 C++ 中的关键字。不允许作为标识符。 【参考方案1】:

你应该只有一个std::vector&lt;A*&gt;,然后当你想在里面放一个BC,你可以这样做:

 std::vector<A*> vec;
 vec.push_back(b);

这将比您目前正在做的任何事情都好得多。

此外,您应该使用std::unique_ptrstd::shared_ptr,具体取决于指针的所有权语义。拥有原始指针是不行的!!

【讨论】:

vec.push_back(b) 不就好了吗? 它会 - 编译器将生成从派生类到其基类的隐式转换。不过static_cast&lt;A*&gt; 不会有什么坏处。 @Marko 如果bB *,则无需转换任何内容。 我想为这个答案投票,但最后一句话太宽泛,不再是好建议。 @BenVoigt 我已经更改了最后一句话。【参考方案2】:

这有效吗?

不,不是。如果您访问它会发生什么,这是完全未定义的。

还有其他方法吗?

是的。这取决于你想做什么:

    复制内容:std::copy(vecC.begin(), vecC.end(), std::back_inserter(vecA));

    如果给定C* 或任何其他派生类型的容器或迭代器,则创建一个充当A* 随机访问容器的适配器

    我相信还有其他解决方案,只要告诉我们你想做什么

【讨论】:

“完全未定义”仅在形式上。在实践中,vector 没有可能造成严重破坏的特殊专业化,指针值偏移也没有任何差异,基类也不是非多态的,派生类中引入了虚拟(这可能使基类和派生类指向同一对象的指针具有不同的位模式)。总之,必须非常小心。但在实践中,只要小心翼翼,转换就会奏效。也就是说,它允许不好的事情发生。请参阅我的答案。 @Alf:在实践中,这违反了严格的别名,优化后可能会导致各种损坏。 @Alf: 'undefined' 意味着标准没有说明如果您列举的点无效会发生什么 - 它不能保证......正如您正确声称的那样,它甚至可以工作,但是,严格来说,它也属于“未定义” :-) 无论如何它会伤害一个叠加原则,这可能会导致任何其他类型的坏事......【参考方案3】:

您可能需要知道的神奇词汇是“协变”和“逆变”。查看here 以了解非常相关的问题。

简短的回答:没有简洁、明智的方法来进行您只想通过强制转换进行的转换。您需要将旧向量的内容复制到新向量中。

但更重要的是:1. 永远不要在标准容器类型上使用new。 2. 不要传递指向标准容器类型的指针。 3. 不要将原始指针传递给您自己的对象,而是使用std::shared_ptr

【讨论】:

您的所有三个离别建议都过于简单且不正确。 @BenVoigt:这是一条非常无益的反馈。干得好。 @nahpr:是的,通过引用传递是很明智的。 @Rook:对不起,我只看你的网名而不是你的声誉,以为你是一个更有经验的用户。无论如何:(1)在标准容器类型上使用new,然后将指针粘贴在 RAII 智能指针内是完全合理的。 (2) 如果引用传递是合理的,那么指针传递也是合理的。我们不是在谈论临时人员的生命周期延长(仅供参考)。 (3) 原始指针是某些容器的迭代器类型,将它们用作迭代器是很有意义的(即非拥有且比容器的生命周期更短) 这是对智能指针并非在所有情况下都最好的原理的另一个很好的阐述:***.com/a/7658089/103167【参考方案4】:

不要通过void*绕道而行,使用C++11只需使用reinterpret_cast

但是,您希望的转换允许非常不好的事情

#include <iostream>
#include <vector>
using namespace std;

class Base ;

class Derived: public Base
 public: int x; Derived(): x(42)  ;

int main()

    vector< Derived* >  v;
    Derived             d;

    v.push_back( &d );
    cout << v.back()->x << endl;        // 42

    vector< Base* >*    pbv =
        reinterpret_cast< vector< Base* >* >( &v );
    Base                b;

    pbv->push_back( &b );
    cout << v.back()->x << endl;        // indeterminate value

这与将 T** 转换为 T const** 时遇到的问题大致相同(不允许,可能会出现不良情况)。简而言之,除非您确切知道自己在做什么,否则不要这样做。越长,嗯,嗯,这里没有讨论的余地,但是涉及到区分可变集合和不可变集合,并且非常非常小心。


编辑:正如其他人(没有解决转换问题)已经说过的那样,一个实际的解决方案是A 指针或智能指针的向量,然后使用 C++ 多态性(即虚拟成员函数)。

【讨论】:

-1 表示“不好的事情”只能在写入容器时发生。 @Ben:也许您正在考虑严格的别名规则。这是特定编译器(即 gcc)的损坏编译器选项,而不是损坏的语言。请注意,有时为了支持 g++ 的行为而引用的支持转换列表从未包括从 struct 的第一个元素到 struct 本身的转换,反之亦然,这在标准的其他地方得到了明确支持。即该列表从未完整,它必须是为了成为有时声称的支持。 @Alf:通过reinterpret_cast 创建的指针的任何取消引用都是未定义的行为。即使内存布局恰好匹配,严格的别名恰好是您的代码可能会中断的一种方式。不,严格的别名是语言的一部分。它不是“损坏的语言”,但确实会使您建议的代码损坏。因为那里有很多损坏的代码,gcc 提供了禁用与别名分析相关的优化的选项,但严格的别名规则不是 gcc 特定的。 @Ben:你说得对,reinterpret_cast 是正式的未定义行为。但它是要使用的语言:所有三个主要桌面平台上的系统软件都依赖于定义这种行为的编译器。不,严格别名不是语言的一部分,标准中也没有提到。再次阅读我写的内容(列表不完整)。我没有添加更强大的论据,因为没有空间来讨论它,并且列表的形式不完整性本身就足够了,但请放心,这完全是 gcc 选项问题,就像该编译器中的浮点优化一样。【参考方案5】:

在我看来,您好像在寻找covariant 对泛型(模板)类型的支持。 C++ 根本不支持这一点。 CLR - and C#/VB 支持它 - 尽管仅在最新版本中。

尽管如此,为了回应其他人的回应,你很可能找错了人。我的预感是你想要一个指向 A 类型对象的指针向量......并且这种类型应该包括虚拟方法和虚拟析构函数 - 根据需要。如果不更好地了解您要解决的高级问题,就不可能更具体地了解合适的替代方法 - 这在您的问题中并不明显。

【讨论】:

以上是关于使用 *void 作为 static_cast 的缓冲区的主要内容,如果未能解决你的问题,请参考以下文章

VS 2010 中的 C++ 中的 static_cast 无法从 void* 转换为 size_t 错误

static_casting 中等数量的 T* 到 void* 时访问冲突

error C2440: “static_cast”: 无法从“void (__thiscall CMainFrame::* )

错误 C2440:“static_cast”:无法从“void (__thiscall Visualizza::*)(char [])”转换为“AFX_PMSG”

为啥我可以将 static_cast void* 转换为 int* 而不能转换为 int*&?

使 .natvis 将 SmartPointer<T> 显示为 static_cast<T*>(void*)