我有课foo
使用小缓冲区优化 (SBO) 管理数据。
当size < 16
,数据保存在本地(在 buffer
中),否则存储在堆上,在 reserved
中持有分配的空间。
class foo {
static const int sbo_size = 16;
long size = 0;
char *ptr;
union {
char buffer[sbo_size];
long reserved;
};
public:
foo()
{
for (int i = 0; i < sbo_size; ++i)
buffer[i] = 0;
}
void clone(const foo &f)
{
// release 'ptr' if necessary
if (f.size < sbo_size)
{
memcpy(this, &f, sizeof(foo));
ptr = buffer;
} else
{
// handle non-sbo case
}
}
};
关于clone()
的问题:
对于 SBO 情况,编译器可能不清楚 union::buffer
将被使用。
使用 memcpy
是否正确并设置ptr
相应地?
最佳答案
如果您可以使用 C++17,我会使用 std::variant
来回避任何潜在的类型双关问题。代替 union 。
虽然这在内部使用了少量存储来跟踪它包含的当前类型,但总体而言这可能是一个胜利,因为您的 ptr
变量可以消失(尽管它应该在你的 union
中)。
它也是类型安全的,即 union
不是(因为如果变体不包含所需的类型, std::get
将抛出异常),并且只需分配给它即可跟踪它包含的数据类型。
生成的类片段可能看起来像这样(毫无疑问,这个代码可以改进):
class foo
{
private:
static const size_t sbo_size = 16;
using small_buf = std::array <char, sbo_size>;
size_t size = 0;
std::variant <small_buf, char *> buf = { };
public:
void clone (const foo &f)
{
char **bufptr = std::get_if <char *> (&buf);
if (bufptr)
delete [] *bufptr;
size = f.size;
if (size < sbo_size)
buf = std::get <small_buf> (f.buf);
else
{
buf = new char [size];
std::memcpy (std::get <char *> (buf), std::get <char *> (f.buf), size);
}
}
};
注释:
你会看到我使用了
std::array
而不是 C 风格的数组,因为std:array
具有许多 C 风格数组所没有的优秀功能为什么
clone
而不是复制构造函数?如果你想要
foo
拥有empty
状态(例如,在默认构造之后),那么您可以查看奇怪命名的std::monostate
.对于原始存储,
std::byte
可能比char
更受青睐.
完整的示例 here .
编辑:为了回答所提出的问题,我不是语言律师,但在我看来,在 clone
内,编译器不知道 f
的事件成员是什么实际上,它可能是从外太空跳伞而来的。
在这种情况下,我希望编译器编写者能够谨慎行事,并将 union 体的活跃成员设置为“不知道”,直到出现一些具体信息。但是(这是一个很大的但是),我不想把我的衬衫押在这一点上。这是一项复杂的工作,编译器编写者确实会犯错误。
因此,本着分享的精神,这里对您的原始代码进行了稍微修改的版本,修复了这个问题。我也搬家了ptr
在你的 union 内,因为它显然属于那里:
class foo {
static const int sbo_size = 16;
long size = 0;
union {
std::array <char, sbo_size> buffer; // changing this
char *ptr;
long reserved;
};
public:
foo()
{
for (int i = 0; i < sbo_size; ++i)
buffer[i] = 0;
}
void clone(const foo &f)
{
// release 'ptr' if necessary
if (f.size < sbo_size)
{
buffer = f.buffer; // lets me do this
ptr = buffer.data ();
} else
{
// handle non-sbo case
}
}
};
所以你可以看到,通过使用 std::array
对于 buffer
(而不是那些黑客 C 风格数组之一),您可以直接分配给它(而不是必须求助于 memcpy
),编译器将然后使其成为您 union 的活跃成员你应该是安全的。
总之,这个问题实际上毫无意义,因为人们不应该(永远)需要编写这样的代码。但毫无疑问有人会立即想出一些东西来证明我错了。
关于c++ - 在带有 union 的类中使用 'memcpy()',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71485076/