无效*是否有合法用途?


87

是否有合法使用 void*在C ++中吗?还是因为C引入了它?

回顾一下我的想法:

输入:如果要允许多种输入类型,则可以重载函数和方法,或者可以定义一个通用的基类或模板(感谢您在答案中提及)。在这两种情况下,代码都具有更多的描述性和更少的错误发生(假设基类以合理的方式实现)。

输出:我想不出比void*从已知基类派生的东西更喜欢接收的任何情况 。

只是为了弄清楚我的意思:我并不是在问是否有的用例void*,而是是否有void*最佳或唯一选择的用例。以下几个人已完美地回答了这个问题。


3
您想拥有int和std :: string等多种类型的时间怎么样?
阿米尔(Amir)

12
@Amir, ,variantany标签联合。能告诉您内容的实际类型和更安全使用的任何内容。
Revolver_Ocelot 2015年

15
“ C有它”是足够有力的理由,无需寻找更多。无论哪种语言,尽可能避免它是一件好事。
n。代词

1
一件事:没有C风格的API互操作就很尴尬。
马丁·詹姆斯

1
一个有趣的用法是指针向量的类型擦除
Paolo M

Answers:


81

void*至少必须是::operator new(也是每个operator new...)的结果,malloc并且作为放置new运算符的参数。

void*可以认为是每种指针类型的通用超类型。因此,这并不完全是指向的指针void,而是指向任何内容的指针。

顺便说一句,如果您想保留一些不相关的全局变量的数据,std::map<void*,int> score; 则可以在声明了global int x;double y;and,std::string s;doscore[&x]=1;score[&y]=2;and之后使用score[&z]=3;

memset 要一个 void*地址(最通用的地址)

另外,POSIX系统具有dlsym,其返回类型显然应为void*


1
这是非常好的一点。我忘了提起我是在考虑语言用户而不是实现方面的问题。我还不明白的一件事是:新功能吗?我将其更多地视为关键字
magu_

9
new是一个运算符,例如+和sizeof。
约书亚

7
当然内存也可以通过返回char *。它们在这个方面非常接近,尽管含义有些不同。
edmz 2015年

@约书亚 不知道 谢谢你教我这个。既然C++是这样写的,C++就有足够的理由了void*。一定喜欢这个网站。问一个问题,学到很多东西。
magu_

3
@black回到过去,C甚至没有一个void*类型。使用了所有标准库函数char*
Cole Johnson

28

有多种原因使用void*,最常见的三个原因是:

  1. void*在其接口中使用C库进行交互
  2. 类型擦除
  3. 表示无类型的内存

相反,用void*(3)而不是char*(或变体)表示未类型的内存有助于防止意外的指针算术;可用的操作很少,void*因此通常需要进行铸造才能使用。当然,很像char*别名没有问题。

C ++仍然将类型擦除(2)与模板结合使用,或者不结合使用:

  • 非通用代码有助于减少二进制膨胀,即使在通用代码中,它也可用于冷路径
  • 非通用代码有时对于存储是必要的,即使在诸如 std::function

显然,当您处理的接口使用void*(1)时,您别无选择。


15

哦,是的。即使在C ++中,有时我们也会这样做,void *而不是template<class T*>因为有时模板扩展中的多余代码会显得过重。

通常,我会将其用作类型的实际实现,并且模板类型将从其继承并包装类型转换。

此外,自定义平板分配器(操作员新的实现)必须使用void *。这就是为什么g ++添加了允许指针算术扩展的原因之一,void *就好像它的大小为1。


谢谢您的回答。您是对的,我忘记提及模板了。但是模板会不会更适合该任务,因为很少会引入性能损失?(stackoverflow.com/questions/2442358/…
magu_

1
模板引入了代码大小的损失,在大多数情况下,这也是性能的损失。
约书亚2015年

struct wrapper_base {}; template<class T> struct wrapper : public wrapper_base {T val;} typedef wrapper* like_void_ptr;是使用模板的最小void- *模拟器。
user253751

3
@约书亚:您将需要引用“多数”。
user541686

@Mehrdad:请参阅L1缓存。
约书亚2015年

10

输入:如果要允许多种输入类型,则可以重载函数和方法

真正。

或者,我们可以定义一个通用的基类。

这在一定程度上是对的:如果你做不到定义公共基类,接口或类似的东西怎么办?要定义那些代码,您需要访问源代码,这通常是不可能的。

您没有提及模板。但是,模板不能帮助您实现多态性:它们与静态类型一起使用,即在编译时已知。

void*可以认为是最低公分母。在C ++中,您通常需要它,因为(我)你不能固有多少与它及(ii)几乎总有更好的解决方案做。

更进一步,您通常最终将其转换为其他具体类型。这char *就是通常更好的原因,尽管它可能表明您期望使用C样式的字符串,而不是纯数据块。这就是为什么void*比这更好的原因char*,因为它允许从其他指针类型隐式转换。

您应该接收一些数据,使用它并产生输出。为此,您需要了解正在使用的数据,否则您将遇到另一个问题,这不是您最初要解决的问题。例如,许多语言都没有void*,也没有问题。

另一合法用途

当使用诸如printf指针之类的功能打印指针地址时,其void*类型应为类型,因此,您可能需要强制转换为void*


7

是的,它与该语言中的其他任何东西一样有用。
例如,您可以使用它来擦除一个类的类型,该类可以在需要时静态转换为正确的类型,以便具有最小化和灵活的界面。

响应中,有一个使用示例可以使您有所了解。
为了清楚起见,我将其复制并粘贴到下面:

class Dispatcher {
    Dispatcher() { }

    template<class C, void(C::*M)() = C::receive>
    static void invoke(void *instance) {
        (static_cast<C*>(instance)->*M)();
    }

public:
    template<class C, void(C::*M)() = &C::receive>
    static Dispatcher create(C *instance) {
        Dispatcher d;
        d.fn = &invoke<C, M>;
        d.instance = instance;
        return d;
    }

    void operator()() {
        (fn)(instance);
    }

private:
    using Fn = void(*)(void *);
    Fn fn;
    void *instance;
};

显然,这只是的用途之一void*


4

与返回指针的外部库函数接口。这是一个Ada应用程序。

extern "C" { void* ada_function();}

void* m_status_ptr = ada_function();

这将返回一个指向Ada想要告诉您的内容的指针。您不必对此做任何花哨的操作,您可以将其还给Ada以做下一件事。实际上,在C ++中解开Ada指针并非易事。


auto在这种情况下,我们不能使用它吗?假设类型在编译时是已知的。
magu_

啊,如今我们可能可以。该代码来自几年前我做一些Ada包装程序时的代码。
RedSonja

Ada类型可能很恶魔-您知道,它们会自己制造,并在使其变得棘手时感到不快。不允许我更改接口,这太容易了,它返回了隐藏在那些空指针中的讨厌的东西。啊。
RedSonja

2

简而言之,C ++作为一种严格的语言(不考虑诸如malloc()这样的C遗留物)需要void *,因为它没有所有可能类型的公共父代。例如,与ObjC不同,它具有object


malloc并且new都返回void *,因此即使C ++中有一个对象类,您也需要
Dmitry Grigoryev

malloc是遗留的,但是在严格的语言中,new应该返回对象*
nredko 2015年

那如何分配整数数组呢?
德米特里·格里戈里耶夫

@DmitryGrigoryevoperator new()返回void *,但是new表达式不返回
MM

1.我不确定我想在每个类和类型之上看到一个虚拟基类对象,包括2.如果它是非虚拟EBC,那又有int什么不同void*呢?
洛罗

1

我想到的第一件事(我怀疑是上面几个回答的具体情况)是将对象实例传递给Windows中的threadproc的功能。

我有几个需要执行此操作的C ++类,它们具有工作线程实现,并且CreateThread()API中的LPVOID参数获取该类中静态方法实现的地址,以便工作线程可以完成此工作。类的特定实例。在threadproc中进行简单的静态强制回退即可产生要使用的实例,从而允许每个实例化的对象从单个静态方法实现中获得一个工作线程。


0

在多重继承的情况下,如果您需要获取指向对象占用的内存块的第一个字节的指针,则可以dynamic_cast指向void*

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.