【问题标题】:Is the std::array bit compatible with the old C array?std::array 位是否与旧的 C 数组兼容?
【发布时间】:2017-01-15 13:15:54
【问题描述】:

std::array<T,N> vT u[N] 的底层位表示是否相同?

换句话说,将N*sizeof(T) 字节从一个字节复制到另一个字节是否安全? (通过reinterpret_castmemcpy。)

编辑:

为了澄清,重点是相同的位表示reinterpret_cast

例如,假设我在一些可复制的类型T 上拥有这两个类,对于一些N

struct VecNew {
    std::array<T,N> v;
};

struct VecOld {
    T v[N];
};

还有遗留功能

T foo(const VecOld& x);

如果表示相同,那么这个调用是安全的并且避免了复制:

VecNew x;
foo(reinterpret_cast<const VecOld&>(x));

【问题讨论】:

  • 您是使用data/&amp;array_name[0] 还是使用“数组”本身的名称进行复制?
  • 不通过reinterpret_cast,因为严格的别名。
  • 嗯...原来的问题是关于复制的,新的问题是关于reinterpret_cast-ing。这有点不同......
  • 看起来您正试图通过用新结构替换旧结构来现代化遗留 C++ 代码,对吧?
  • 然后有人更改VecNew,例如添加新字段并享受调试。不,谢谢。

标签: c++ c++11 memcpy reinterpret-cast


【解决方案1】:

这并不能直接回答你的问题,但你应该简单地使用std::copy

T c[N];
std::array<T, N> cpp;

// from C to C++
std::copy(std::begin(c), std::end(c), std::begin(cpp));

// from C++ to C
std::copy(std::begin(cpp), std::end(cpp), std::begin(c));

如果T 是一个可简单复制的类型,则它将编译为memcpy。如果不是,那么这将按元素进行复制分配并且是正确的。无论哪种方式,这都是正确的,并且可读性很强。无需手动字节算术。

【讨论】:

  • nitpick: std::copy 并不总是编译成 memcpy 这是一个实现细节。例如 VC++ 使用memmove 进行字节复制。
  • 我很伤心。这是一个很好的答案......对于不同的问题!
【解决方案2】:

std::array 提供方法data() 可用于复制到/从适当大小的 c 样式数组:

const size_t size = 123;
int carray[size];
std::array<int,size> array;

memcpy( carray, array.data(), sizeof(int) * size );
memcpy( array.data(), carray, sizeof(int) * size );

documentation所述

此容器是一种聚合类型,其语义与将 C 样式数组 T[N] 作为其唯一非静态数据成员的结构相同。

因此,内存占用似乎与 c 样式数组兼容,但尚不清楚为什么要在没有任何开销的正确方法时对 reinterpret_cast 使用“hacks”。

【讨论】:

  • 这正是我想澄清的“看起来”部分。
  • 你没有回答为什么
【解决方案3】:

我说可以(但标准不保证)。

根据[数组]/2:

数组是一个聚合 ([dcl.init.aggr]),它可以 列表初始化,最多包含 N 个类型可转换为的元素 T.

还有[dcl.init.aggr]:

一个聚合是一个数组或一个类(子句[class])与

  • 没有用户提供的、显式的或继承的构造函数 ([class.ctor]),

  • 没有私有或受保护的非静态数据成员(条款 [class.access]),

  • 没有虚函数([class.virtual]),并且

  • 没有虚拟、私有或受保护的基类 ([class.mi])。

鉴于此,“可以列表初始化”只有在类的开头没有其他成员且没有 vtable 时才有可能。

那么,data() 被指定为:

constexpr T* data() noexcept;

返回:一个指针,使得[data(), data() + size()) 是一个有效范围,data() == addressof(front())

标准基本上想说“它返回一个数组”,但为其他实现敞开大门。

唯一可能的其他实现是具有单个元素的结构,在这种情况下,您可能遇到别名问题。但在我看来,这种方法只会增加复杂性。将数组展开到结构中没有任何好处。

因此,将std::array 实现为数组没有意义

但确实存在漏洞。

【讨论】:

  • 我不同意可能会出现混叠问题。你的理由是什么?
  • 结构和数组在严格别名方面是不兼容的类型。
  • 我不认为您对严格别名规则的解释是正确的。如果是这样,那么数组类型也将与其元素类型不兼容,这显然是荒谬的。
  • 他关于严格别名的断言并不意味着你声称它做了什么。
  • @Brian 这不是 RustyX 所说的。数组从未与具有相同数量的相同类型成员的struct 兼容。但是,即使您关于数组指针与指向其元素的指针的兼容性的切线推断也很快就会变得太真实了!请参阅 ecatmur 关于草稿 P0137R1 中店内乐趣的回答。如果您愿意并且有能力,请提交一份国家机构评论,表达对它的怀疑。
【解决方案4】:

data() 方法的要求是它返回一个指针 T*,这样:

[data(), data() + size()) 是有效范围,data() == addressof(front())

这意味着您可以通过data() 指针顺序访问每个元素,因此如果T 可以轻松复制,您确实可以使用memcpysizeof(T) * size() 字节复制到/从数组T[size()],因为这相当于memcpy单独处理每个元素。

但是,您不能使用reinterpret_cast,因为这会违反严格的别名,因为data() 不需要实际上 由数组支持 - 而且,即使您要保证std::array 包含一个数组,因为 C++17 您不能(即使使用 reinterpret_cast)将指向数组的指针转换为指向/从指向其第一个成员的指针(您必须使用 std::launder)。

【讨论】:

  • Re "因为 C++17 你不能(即使使用 reinterpret_cast)将指向数组的指针转换为指向/从指向其第一个成员的指针的指针(你必须使用 std::launder)",即听起来很有趣:委员会发疯了!请提供更多信息。与此同时,我会做一些爆米花。
  • @Cheersandhth.-Alf “指向数组的指针不能转换为指向/从指向其第一个元素的指针”:见open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0137r1.html
  • 嗯,重新链接,这是一堵文字墙。几百公里。你能模糊地指出一个小于 203 米的区域吗?
  • 这似乎是为了让编译器供应商在标准管理中拥有控制权,由此编译器的缺点和愚蠢的行为变得标准化。哦,好吧。
  • @underscore_d 这不是关于危险,而是关于优化;如果编译器假设不同大小的数组和指针即使元素类型为相同的。这种产生的速度提升(编译器作者认为,并且公平地说,对他们编写科学的、Fortran 风格的代码的客户而言)值得他们的用户编写更多面向系统或面向对象的代码的潜在混淆和破坏。
【解决方案5】:

array 对实例化它的底层类型没有太多要求。

为了使使用memcpyreinterpret_cast 进行复制获得任何有用结果的可能性,您实例化它的类型必须是可简单复制的。将这些项目存储在 array 中不会影响 memcpy(等)仅适用于普通可复制类型的要求。

array 必须是一个连续容器 和一个聚合,这几乎意味着元素的存储必须是一个数组。标准显示为:

T elems[N]; // exposition only

然而,它后来有一个注释,至少暗示它是一个数组是必需的(§[array.overview]/4):

[注意:成员变量elems 仅用于说明,以强调array 是一个类聚合。 名称elems 不是数组接口的一部分。 —end note]

[强调添加]

请注意,实际上只有特定名称 elems 不是必需的。

【讨论】:

  • new draft 去掉了那部分。现在我们只知道它是一个可以用NTs(但+1)初始化列表的聚合。
  • @Barry:我完全不确定这真的会发生很大变化。顺便说一句,我看不到满足其要求(连续容器、聚合)的方法,除非只有一个数据成员,即一个数组。我想如果你能保证元素之间不填充,你可以创建一个离散元素的可变参数模板,但这只是因为元素仍然可以像数组一样寻址。
  • 如果 array 不是原始数组的简单 struct 包装器,则初始化无法工作。
  • @JerryCoffin 哦,我不是说std::array 绝对不是原始数组的包装器。我只是说现在围绕该描述的措辞完全不同(不确定这个机会的意义是什么。只是指出来)。
  • 如果存储是按正确顺序的离散成员,则初始化(但不是其他部分)可以工作。
猜你喜欢
  • 1970-01-01
  • 2021-12-10
  • 1970-01-01
  • 2012-02-13
  • 1970-01-01
  • 2017-06-19
  • 2016-07-02
  • 1970-01-01
  • 2011-05-24
相关资源
最近更新 更多