我需要使用一个非常大且复杂的仅限标题的类(想想boost :: multiprecision :: cpp_bin_float< 76>,下面称为BHP
),我想隐藏在类似pimpl的实现背后,纯粹是为了减少一个大型项目中的编译时间(用std::complex<double>
替换Boost类减少了大约50%的编译时间。)
但是,我想避免动态内存分配。因此,这样的事情似乎很自然(暂时忽略对齐问题,使用aligned_storage
或alignas
可以避免这种问题):
struct Hidden {
char data[sz];
Hidden& punned(Hidden const& other);
};
然后可以在单个翻译单元中定义 Hidden::punned
以将data
强制转换为BHP*
,对其执行操作并且不会污染具有170k LOC头文件的所有其他翻译单元。可能的实现可能是
Hidden& Hidden::punned(Hidden const& other) {
*(BHP*)(data) += *(BHP*)(other.data);
return *this;
}
当然,这是未定义的行为,因为我们通过类型为BHP
的指针访问类型为char
的对象,因此违反了严格的别名规则。正确的方法是:
Hidden& Hidden::proper(Hidden const& other) {
BHP tmp; std::memcpy(&tmp, data, sz);
BHP tmp2; std::memcpy(&tmp2, other.data, sz);
tmp += tmp2;
std::memcpy(data, &tmp, sz);
return *this;
}
现在看来“显而易见”可以优化这些memcpy
次调用。不幸的是,情况并非如此,它们仍然存在并使proper()
远大于punned()
。
我想知道a)将数据直接存储在Hidden
对象中的正确方法是什么,b)避免不必要的副本重新解释它和c)避免违反严格对齐规则和d )不要携带指向存储区域的额外指针。
有一个godbolt link here;请注意,我测试的所有编译器(GCC 4.9 - trunk,Clang 3.9,4.0和5.0以及Intel 18)都没有“优化”memcpy。某些版本的GCC(例如5.3)也完全抱怨违反严格别名规则,但并非所有版本都这样做。我还插入了一个知道Direct
的{{1}}类,因此可以直接调用它,但我想避免这种情况。
最小的工作示例:
BHP
答案 0 :(得分:1)
当然,这是未定义的行为,因为我们通过char类型的指针访问类型为
char*
的对象。
事实并非如此。通过BHP
is fine 进行访问,前提是实际上存在new (data) BHP(...);
个对象。也就是说,只要双方都有:
*(BHP*)(data) += *(BHP*)(other.data);
然后这是完全可以的:
alignas(BHP)
确保您的char数组也是char[]
。
请注意,gcc在您有时重新解释{{1}}时不喜欢,因此您可以选择使用std::aligned_storage_t
之类的内容。