【问题标题】:Is void* necessary apart from memory allocation related stuff除了内存分配相关的东西之外, void* 是必需的吗
【发布时间】:2011-11-10 01:30:55
【问题描述】:

除了 C++ 中与内存分配相关的内容之外,void* 是否必要? 可以举个例子吗?

【问题讨论】:

  • 嗯,内存分配语言中非常重要的一部分...
  • 我并不是说内存分配不重要,它在 c++ 中非常重要。只是想知道是否还有其他频繁使用...
  • 是的,我明白了。我只是想建议 void 指针可能会比你意识到的更频繁地出现,例如任何时候你说std::vector
  • 你指的是vector内部增长吗?
  • void* 是任何operator new() 和任何分配器的分配函数的返回类型,几乎所有标准容器都使用该函数,因此引擎盖下有很多空指针。

标签: c++ void-pointers


【解决方案1】:

记录内存地址

如果您想使用 iostreams 输出指针(例如用于日志记录),那么通过 void* 是确保 operator<< 没有以某种疯狂的方式过载的唯一方法。

#include <iostream>

struct foo {
};

std::ostream& operator<<(std::ostream& out, foo*) {
  return out<<"it's a trap!";
}

int main() {
  foo bar;
  foo *ptr = &bar;

  std::cout << ptr << std::endl;
  std::cout << static_cast<void*>(ptr) << std::endl;
}

测试 iostream 状态

iostreams 重载operator void* 作为状态检查,因此if (stream)while (stream) 之类的语法是测试流状态的简便方法。


模板元编程

有时您可能希望将 void* 与模板元编程一起使用,以减少包罗万象,例如使用 SFINAE 技巧,但通常使用一种或另一种形式的部分专业化有更好的方法。


访问大多数派生指针

正如 Alf 在 cmets 中指出的那样,dynamic_cast&lt;void*&gt; 对于在层次结构中获取最多派生类型也很有用,例如:

#include <iostream>

struct other {
  virtual void func() = 0;
  int c;
};

struct foo {
  virtual void func() { std::cout << "foo" << std::endl; }
  int a;
};

struct bar : foo, other {
  virtual void func() { std::cout << "bar" << std::endl; }
  int b;
};

namespace {
  void f(foo *ptr) {
    ptr->func();
    std::cout << ptr << std::endl;
    std::cout << dynamic_cast<void*>(ptr) << std::endl;
  }

  void g(other *ptr) {
    ptr->func();
    std::cout << ptr << std::endl;
    std::cout << dynamic_cast<void*>(ptr) << std::endl;
  }
}

int main() {
  foo a;
  bar b;
  f(&a);
  f(&b);
  g(&b);
}

给予:

foo
0xbfb815f8
0xbfb815f8
bar
0xbfb815e4
0xbfb815e4
bar
0xbfb815ec
0xbfb815e4

在我的系统上。


例外情况

§ 15.3.1 规定:

异常声明不应表示指针或引用 不完整的类型,除了 void*、const void*、volatile void* 或 const volatile void*.

因此,捕获指向不完整类型的指针似乎是唯一合法的方法是通过void*。 (虽然我认为如果你真的需要使用它可能会有更大的问题)


传统 C 使用

void* 有很多“传统”C 用途,用于在不知道数据是什么的情况下存储指向数据的指针,但在新的 C++ 代码中,几乎总是有更好的方式来表达相同的功能。

【讨论】:

  • 另外,dynamic_cast&lt;void*&gt;(p) 是获取最多派生对象的唯一通用方法。
  • 你能举一个例子说明在模板元编程中 void* 是必要的吗?
  • @Dennis - 我想不出一个不太人为的例子,这就是为什么我没有包括任何例子。我的想法是三向“它是什么?”结果可能是特定类型、指针或“其他”的事物类型,即T*void*...,但我知道将其描述为必要这有点牵强。但这并不意味着没有一个很好的例子,而且我肯定会说它在任何元编程问题的技巧中都有一席之地。
【解决方案2】:

void * 在 C++ 中用于表示指向未知结构的指针类型。

所以每当我有一个指针类型时,我都会使用它,而代码不应该知道其中的内容:

  • 内存分配
  • 容器
  • ...

通常 void * 与模板代码混合得很好,以避免模板引起的代码膨胀。

想象你实现了一个

template <typename T>
class vector {
 /*stuff */
};

然后您可以为 T * 创建一个模板特化,它使用 void 指针,这样代码就不会被重复。

【讨论】:

  • 你能详细说明它是如何避免重复的吗?你说的是输出吧?据我所见,代码 will 会被复制,因为仍然会为每个 T 合成代码,但可能存在编译器无法发现的错误。
  • @awoodland:&lt;T*&gt; 特化的方法将static_cast&lt;&gt;T*void*,然后调用对应的method on the void*` 特化。因此,这些&lt;T*&gt; 方法将是微不足道和内联的,只留下对&lt;void*&gt; 模板方法的调用。
【解决方案3】:

void* 经常用于回调。

回调一般在C语言中使用函数实现 指针和辅助用户定义数据作为 void 指针传递 通用性。

[from here]

当然,这不是类型安全的,所以人们想出了包装这种东西的方法,like here

【讨论】:

  • 这对 C 来说是有意义的,但在 C++ 中它充其量只是“遗留”
  • @awoodland:我认为这不仅仅是传统。如果你想对有状态的回调进行类型擦除,就像boost::function 对仿函数所做的那样,那么我希望在某处埋有一些void*。但是,您当然希望避免在公共接口中使用函数指针/用户数据对。
【解决方案4】:

外部库经常使用它们(尤其是在 C 中)。

在这种情况下,我通常会隐藏这些库的使用(或更危险的部分)。我通过在它们周围编写接口(如包装)来隐藏它们。我编写的接口用于将类型安全引入程序。在这种情况下,void* 可能是必需的,但至少它是隐藏的并仅限于方法或回调。

【讨论】:

  • 我会说与使用指向已声明但未定义结构的指针相比,它的类型安全性较低,因为 anything 可能会被意外传入!
【解决方案5】:

您可能希望使用 void 的一种情况是在传递数据缓冲区时,例如在此函数中:

void loadData(void* data, std::size_t size)

很多代码通过 char 指针而不是 void 指针来传递缓冲区,因为通常以 1 字节块读取缓冲区,这恰好是 C++ 标准确保 char 具有的大小。

但是,使用 void-pointers 更为通用。这是函数告诉你的一种方式,“只需给我一些数据,不要担心我会如何阅读它”。然后,该函数可以转换指针并以块或任何它喜欢的大小读取数据。

【讨论】:

  • 您也可以分块阅读char*。如果你曾经在 C++ 中以这种方式请求数据,我会打你的头(真的)。
  • @Matthieu 这有什么不好?
  • 在 C 中,这几乎是惯用的,但是 1/ C++ 是围绕类型安全构建的,虽然您可以忽略它,但如果这样做,则需要付出高昂的代价(维护); 2/ 将缓冲区与其大小分开总是很尴尬,即使你绕过类型安全,你至少可以以一种不会绕过内存安全的方式来做......围绕该缓冲区及其大小构建一个类,所以你确定大小不会不同步。
  • @Paul:memcpy 可以,但是对于任何 I/O 目的,使用 void* 是不安全的,因为这会让调用者传入 unsigned char*signed char* 而不是char*。是的,好的,每个人都使用二进制补码,实际上可以这样重新解释,但是 C++ 标准假装并非每个人都这样做,因此任何与此相关的安全性不足都会通知标准,并且告知设计为“可移植”的代码和接口的样式。将“可移植”定义为“适用于任何符合标准的实现”可能过于宽泛,但至少是简洁的。
  • 无论如何,我认为这就是为什么basic_istream::read 采用charT*,因此istream 采用char* 的部分原因。与采用 void* 的旧 C 风格 fread 相比,它为您提供了一点点额外的类型安全性。
【解决方案6】:

不,不是。引用非类型化内存只是一个习惯用法

【讨论】:

  • 我也认为避免你不想做的意外指针算术很有用。
  • 我认为,当谈到编写代码的最佳方式(应始终使用静态类型)以及我们理解'只是在谈论 storage 而不是将其用于例如转换或特定于指针地址的类型的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-19
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 2017-11-25
相关资源
最近更新 更多