【问题标题】:Take a reference to a pointer with erased type (void*)引用具有擦除类型 (void*) 的指针
【发布时间】:2020-11-01 19:15:09
【问题描述】:

我可以从T* 中获取T*&。现在我需要以类型擦除的方式存储我的T*,更具体地说是void*。我可以从void* 中获取T*& 吗? (当然知道我的void* 确实指向Ts)

例子:

#include <iostream>
#include <cstdlib>
#include <numeric>

int main() {
  int n = 10;
  void* mdbuf = malloc(n*sizeof(double));
  double* mdarr = (double*)mdbuf;
  std::iota(mdarr,mdarr+n,0.); // initialize the memory with doubles

  // solution 1: works but not what I want since I want to refer to the type-erased mdbuf variable
  double*& mdarr_ref = mdarr; // ok, now mdarr_ref refers to variable mdarr
  
  // solution 2: does not compile
  double*& mdbuf_ref = (double*)mdbuf; // error: cannot bind non-const lvalue reference of type 'double*&' to an rvalue of type 'double*'

  // solution 3: compiles and work but I want to be sure this is not out of pure luck: is it undefined behavior?
  double*& mdbuf_ref = (double*&)mdbuf; // we would like mdbuf_ref to refer to variable mdbuf. It compiles...
  std::iota(mdbuf_ref,mdbuf_ref+n,100.);
  for (int i=0; i<n; ++i) {
    std::cout << mdbuf_ref[i] << ", "; // ...does what we want in this case... is it valid however?
  }
}

编辑:也许一种看待它的方式如下:

double d;
void* v_ptr = &d;
double* d_ptr = (double*)v_ptr; // (1) valid
double& d_ref = d; // (2) valid
double& d_ref2 = (double&)d; // (3) valid? Should be the same as (2) ?
double*& d_ref3 = (double*&)v_ptr; // (4)

问题是:(4) 有效吗?如果(1)和(3)成立,它只是链接两者,所以我希望它是有效的,但我想要一些证据

【问题讨论】:

  • 你有一个引用有意义的用例吗?
  • 不,这是不合法的。在字可寻址系统上,sizeof(void*) != sizeof(double*)void* 由地址 + 字节索引组成,而不仅仅是地址。
  • @RaymondChen 我很惊讶。你有资源吗 ?不合法是什么意思?实现定义或未定义的行为?在我关心的所有系统上sizeof(void*) == sizeof(double*)
  • 在您的示例中,您的任何地方都没有double 类型的对象(我猜是您的T)。仅仅因为你为它保留空间并不意味着你有一个对象。因此,尽管如果您创建这样的对象(如在解决方案 1 中),您可以获取对 double* 的引用,但您将永远无法使用该指针执行任何操作。 (语言律师标签)
  • @Bérenger 为什么你仍然坚持 mallocing 这个东西?如果你使用new(或者甚至使用std::arraystd::vector)正确地做到这一点,你就已经有了一个有效的对象和一个具有正确类型的指向它的指针。

标签: c++ language-lawyer


【解决方案1】:

我将采用您的第二个示例并使用别名重写其中的部分内容,以更好地说明您的要求。

using V = void*;
using K = double*;

double d;
V v_ptr = reinterpret_cast<V>(&d);
V &v_ptr_ref1 = v_ptr; //Refers to the `V` object denoted by `v_ptr`.
K d_ptr = &d;
K &d_ptr_ref1 = d_ptr; //Refers to the `K` object denoted by `d_ptr`.
V &d_ptr_ref2 = reinterpret_cast<V&>(d_ptr);

所以,我们有两种类型:KV。在最后一行,我们使用K 类型的对象初始化对V 的引用。所以d_ptr_ref2被初始化为引用K类型的对象,但是引用的类型是V

它们是否“只是”指针类型并不重要。在 C++ 中,指针是对象类型,它们遵循任何其他对象类型的所有规则。

C++ 的严格别名规则禁止在某些非常特殊的情况之外通过不同类型的泛左值(如引用)访问一种类型的对象。具体例外情况因版本而异,但没有版本的 C++,其中 void*double* 是例外。

尝试访问 d_ptr_ref2 意味着您正在通过不相关类型 V 的引用访问类型为 K 的对象。这违反了严格的别名,因此会产生未定义的行为。

【讨论】:

  • K&amp; d_ptr_ref3 = reinterpret_cast&lt;K&amp;&gt;(v_ptr); 呢?那将是 OP 的情况 (4)
  • @Bérenger:我不知道为什么你会认为反过来不会也违反严格的别名。
【解决方案2】:

您的解决方案 1 是唯一的答案;你can’t lie about the pointer type itself。 (这个问题是关于 C 的,但 C++ 引用的规则是等价的。)

【讨论】:

  • 你有一个链接,其中 C++ 引用规则等同于 C 指针规则吗?
  • @Bérenger:它们在所有情况下并不相同,但简单的“不说谎”规则是 [basic.lval]/11。
  • 我不明白你为什么说“不要说谎”。 mdbuf 确实指向 double 类型(即使它不知道是类型系统),因此将其转换为 double* 并不是说​​谎,它是明确定义的。此外,double d; double&amp; d_ref = (double&amp;)d 也可以(我想,我们实际上不需要那么明确)。那么问题就是将两者链接在一起是否可以。
  • @Bérenger: "mdbuf 确实指向 double 类型" 不,它没有。它指向一片空白的记忆。你从来没有做过 C++ 标准要求你做的任何事情,以便在那里放置 double
  • @NicolBolas 好的,我编辑了帖子以使其指向双打
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-23
  • 2015-02-14
  • 1970-01-01
  • 1970-01-01
  • 2012-03-19
  • 1970-01-01
相关资源
最近更新 更多