我有一个模板类,带有bool作为模板参数Dynamic<bool>
。无论参数是正确还是错误,它都具有完全相同的数据成员。他们的成员功能只是不同的。
我需要暂时将一个情况转换为另一个情况,而不是使用复制/移动构造函数。所以我求助于型号。为了确保这是一个问题,我使用了两个static_asserts
:
d_true=Dynamic<true>(...);
...
static_assert(sizeof(Dynamic<true>)==sizeof(Dynamic<false>),"Dynamic size mismatch");
static_assert(alignof(Dynamic<true>)==alignof(Dynamic<false>),"Dynamic align mismatch");
Dynamic<false>& d_false=*reinterpret_cast<Dynamic<false>*>(&d_true);
...
所以我认为我正在做的事情是安全的,如果有什么问题,编译器会给我一个static_assert
错误。但是,GCC发出警告:
warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
我的问题是双重的:我正在为实现这一目标做的最好方法吗?如果是这样,怎么能说服GCC安全并摆脱警告?
一种明显的可能性是将两个共有的数据分开到自己的类(或struct(中,然后在需要时从对象中获取。
struct Common {
// ...
};
template <bool b>
class Dynamic {
Common c;
public:
Common &get_data() { return c; }
// ...
};
从那里开始,其余的似乎很明显 - 当您需要Dynamic<whatever>
的数据时,请致电get_data()
,然后离开。
当然,关于一般主题有很多变化,例如,您可以使用继承:
struct Common { /* ... */ };
template <bool t>
class Dynamic : public Common {
// ...
};
这消除了额外的 c.
,以前的版本需要每次参考通用数据,但是(至少在我看来(继承可能太高了。
在标准中,"禁止"重新解释从类型A到类型的内存区域。这称为Aliasing。混音有3个例外,相同类型具有不同的CV资格,基本类型和char[]
的区域。(对于char
,贬损仅在char方向上单向式(
如果您使用std::aligned_storage
和新的位置,则可以将该区域重新释放到所需的任何东西中,而无需编译器可以抱怨。这就是variant
的工作方式。
编辑
:好的,上面的实际上是正确的(只要您忘记了std::launder
(,但由于"生命周期"而误导。只有一个对象可以一次存在于存储空间的顶部。因此,通过另一种类型的视图来解释它是不确定的行为。 关键是构造。
如果我可能建议,请转到cppReference,以其static_vector
示例,将其简化为1的情况1。添加一些Getters,恭喜,您已重新发明bitcast
:)(提案http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0476r2.html(。
可能看起来像这样:
#include <type_traits>
#include <string>
#include <new>
#include <cstring>
#include <iostream>
using namespace std;
template< bool B >
struct Dynamic
{
template <bool B2 = B>
void ConditionalMethod(typename enable_if<B2>::type** = 0)
{}
string m_sharedObject = "stuff";
};
int main()
{
using D0 = Dynamic<false>;
using D1 = Dynamic<true>;
aligned_storage<sizeof(D0), alignof(D0)>::type store[1];
D0* inst0 = new (&store[0]) D0 ;
// mutate
inst0->m_sharedObject = "thing";
// pune to D1
D1* inst1 = std::launder(reinterpret_cast<D1*>(&store[0]));
// observe aliasing
cout << inst1->m_sharedObject;
inst0->~D0();
}
请参见Wandbox中的生效
编辑:经过长时间的讨论之后,新标准中还有其他部分" 8.2.1.11",这更好地解释了为什么这不是严格有效的。我建议参考"生命周期"一章。
https://en.cppreference.com/w/cpp/language/lifetime
来自Miles Budnek评论:
该地址没有
Dynamic<true>
对象,通过Dynamic<false>
访问它是未定义的行为。
在阅读了https://stackoverflow.com/a/57318684/2166857中的讨论之后解决我的问题。这仅在
时起作用1(两种类型的对齐和大小匹配
2(两种类型都可以复制(https://en.cppreference.com/w/cpp/named_req/trivalycopyable(
首先使用aligned_storage
定义内存类型typedef std::aligned_storage<sizeof(Dynamic<true>),alignof(Dynamic<true>)>::type DynamicMem;
然后引入该类型的变量
的变量DynamicMem dynamic_buff;
然后使用新的位置将对象启动到主要类(在我的情况下,动态(
new (&dynamic_buff) Dynamic<true>();
然后,只要需要使用reinterpret_cast来定义对象引用或范围中与之关联的指针
{
Dynamic<true>* dyn_ptr_true=reinterpret_cast<Dynamic<true>*>(&dynamic_buff)
// do some stuff with dyn_ptr_true
}
这个解决方案绝不是完美的,但是它确实为我完成了工作。我确实鼓励大家阅读线程https://stackoverflow.com/a/57318684/2166857,然后在@miles_budnek和 @v.oddou之间来回遵循。我当然从中学到了很多。
标准C 中唯一的安全类型punning方法是通过std::bit_cast
。但是,如果编译器无法优化,这可能涉及副本,而不是将相同的内存表示形式视为不同类型。此外,当前std::bit_cast
仅由MSVC支持,尽管您可以使用__builtin_bit_cast
由于您使用的是GCC,因此可以使用属性__may_alias__
来告诉它是安全的
template<int T>
struct Dynamic {};
template<>
struct Dynamic<true>
{
uint32_t v;
} __attribute__((__may_alias__));
template<>
struct Dynamic<false>
{
float v;
} __attribute__((__may_alias__));
float f(Dynamic<true>& d_true)
{
auto& d_false = *reinterpret_cast<Dynamic<false>*>(&d_true);
return d_false.v;
}
clang和ICC也支持该属性。请参阅Godbolt上的演示,没有任何警告