我正在尝试构建一个类模板,它将一堆类型打包在一个适当大的 char 数组中,并允许将数据作为单独的正确类型引用进行访问。现在,根据标准,这可能会导致违反严格别名,从而导致未定义的行为,因为我们正在char[]
通过与其不兼容的对象访问数据。具体来说,标准规定:
如果程序尝试通过非下列类型之一的泛左值访问对象的存储值,则行为未定义:
- 对象的动态类型,
- 对象的动态类型的 cv 限定版本,
- 与对象的动态类型类似(如 4.4 中定义)的类型,
- 与对象的动态类型相对应的有符号或无符号类型,
- 对应于对象动态类型的 cv 限定版本的有符号或无符号类型,
- 聚合或联合类型,在其元素或非静态数据成员中包括上述类型之一(递归地包括子聚合或包含联合的元素或非静态数据成员),
- 一个类型,它是对象的动态类型的(可能是 cv 限定的)基类类型,
- 一个
char
或unsigned char
类型。
鉴于突出显示的要点的措辞,我提出了以下alias_cast
想法:
#include <iostream>
#include <type_traits>
template <typename T>
T alias_cast(void *p) {
typedef typename std::remove_reference<T>::type BaseType;
union UT {
BaseType t;
};
return reinterpret_cast<UT*>(p)->t;
}
template <typename T, typename U>
class Data {
union {
long align_;
char data_[sizeof(T) + sizeof(U)];
};
public:
Data(T t = T(), U u = U()) { first() = t; second() = u; }
T& first() { return alias_cast<T&>(data_); }
U& second() { return alias_cast<U&>(data_ + sizeof(T)); }
};
int main() {
Data<int, unsigned short> test;
test.first() = 0xdead;
test.second() = 0xbeef;
std::cout << test.first() << ", " << test.second() << "\n";
return 0;
}
(上面的测试代码,尤其是Data
class只是一个简单的想法演示,所以请不要指出我应该如何使用std::pair
or std::tuple
。alias_cast
模板还应该扩展为处理 cv 限定类型,它只能是安全的在满足对齐要求时使用,但我希望这个片段足以证明这个想法。)
这个技巧消除了 g++ 的警告(使用 编译时g++ -std=c++11 -Wall -Wextra -O2 -fstrict-aliasing -Wstrict-aliasing
),并且代码可以工作,但这真的是告诉编译器跳过基于严格别名的优化的有效方法吗?
如果它无效,那么如何在不违反别名规则的情况下实现这样的基于 char 数组的通用存储类?
编辑:用这样alias_cast
的简单替换reinterpret_cast
:
T& first() { return reinterpret_cast<T&>(*(data_ + 0)); }
U& second() { return reinterpret_cast<U&>(*(data_ + sizeof(T))); }
使用 g++ 编译时会产生以下警告:
aliastest-so-1.cpp:在 'T& Data::first() 的实例化中 [with T = int; U = short unsigned int]': aliastest-so-1.cpp:28:16:
这里需要 aliastest-so-1.cpp:21:58: 警告:取消引用类型双关指针将破坏严格别名规则 [-严格锯齿]