好吧,让我总结一下整个事情。
您的(正确!)答案表明,在 C++ 中,二进制兼容性 * 从不保证不同类型。获取变量所在的内存区域的值并将其用于不同类型的变量是未定义的行为(这很可能也应避免使用相同类型的变量)。
在现实生活中,即使对于简单对象,这东西也可能很危险,更不用说容器了!
*: 二进制兼容性 我的意思是相同的值以相同的方式存储在内存中,并且以相同的方式使用相同的汇编指令来操作它。例如:即使float 和int 各占4 个字节,它们也不是二进制兼容。
但是我对这个 C++ 规则并不满意:让我们关注一个案例,比如这两个结构:struct A{ int a[1000000]; }; 和 struct B{ int a[1000000]; };。
我们不能只使用A 对象的地址,就好像它是B 一样。这让我很沮丧,原因如下:
编译器静态地知道这些结构是否二进制兼容:一旦生成了可执行文件,您就可以查看它并判断它们是否兼容。只是它(编译器)没有给我们这些信息。
据我所知,曾经存在的任何 C++ 编译器都以一致的方式处理数据。我什至无法想象编译器会为这两种结构生成不同的表示。最让我烦恼的一点是,不仅那些简单的A 和B 结构是二进制兼容的,而且任何容器都是,如果你将它与类型一起使用,你可以期望二进制兼容(我在自定义容器和 STL/boost 容器上使用 GCC 4.5 和 Clang 2.8 进行了一些测试)。
强制转换运算符允许编译器执行我想要执行的操作,但仅限于基本类型。如果您将int 转换为const int(或int* 和char*),并且这两种类型二进制兼容,编译器可以(很可能)避免复制它并使用相同的原始字节。
然后我的想法是创建一个自定义的object_static_cast,它将检查它获得的类型的对象以及要转换的类型的对象是否二进制兼容;如果是,则仅返回转换后的引用,否则将构造一个新对象并将其返回。
希望这个答案不要被太多反对;如果 SO 社区不喜欢它,我会删除它。
为了检查两种类型是否二进制兼容引入了一个新的类型特征:
// NOTE: this function cannot be safely implemented without compiler
// explicit support. It's dangerous, don't trust it.
template< typename T1, typename T2 >
struct is_binary_compatible : public boost::false_type{};
正如注释所说(如前所述),没有办法实际实现这种类型特征(例如 boost::has_virtual_destructor)。
那么这里是实际的object_static_cast 实现:
namespace detail
{
template< typename T1, typename T2, bool >
struct object_static_cast_class {
typedef T1 ret;
static ret cast( const T2 &in ) {
return T1( in );
}
};
// NOTE: this is a dangerous hack.
// you MUST be sure that T1 and T2 is binary compatible.
// `binary compatible` means
// plus RTTI could give some issues
// test this any time you compile.
template< typename T1, typename T2 >
struct object_static_cast_class< T1, T2, true > {
typedef T1& ret;
static ret cast( const T2 &in ) {
return *( (T1*)& in ); // sorry for this :(
}
};
}
// casts @in (of type T2) in an object of type T1.
// could return the value by value or by reference
template< typename T1, typename T2 >
inline typename detail::object_static_cast_class< T1, T2,
is_binary_compatible<T1, T2>::value >::ret
object_static_cast( const T2 &in )
{
return detail::object_static_cast_class< T1, T2,
is_binary_compatible<T1, T2>::value >::cast( in );
};
这里是一个用法示例
struct Data {
enum { size = 1024*1024*100 };
char *x;
Data( ) {
std::cout << "Allocating Data" << std::endl;
x = new char[size];
}
Data( const Data &other ) {
std::cout << "Copying Data [copy ctor]" << std::endl;
x = new char[size];
std::copy( other.x, other.x+size, x );
}
Data & operator= ( const Data &other ) {
std::cout << "Copying Data [=]" << std::endl;
x = new char[size];
std::copy( other.x, other.x+size, x );
return *this;
}
~Data( ) {
std::cout << "Destroying Data" << std::endl;
delete[] x;
}
bool operator==( const Data &other ) const {
return std::equal( x, x+size, other.x );
}
};
struct A {
Data x;
};
struct B {
Data x;
B( const A &a ) { x = a.x; }
bool operator==( const A &a ) const { return x == a.x; }
};
#include <cassert>
int main( ) {
A a;
const B &b = object_static_cast< B, A >( a );
// NOTE: this is NOT enough to check binary compatibility!
assert( b == a );
return 0;
}
输出:
$ time ./bnicmop
Allocating Data
Allocating Data
Copying Data [=]
Destroying Data
Destroying Data
real 0m0.411s
user 0m0.303s
sys 0m0.163s
让我们在main() 之前添加这些(危险的!)行:
// WARNING! DANGEROUS! DON'T TRY THIS AT HOME!
// NOTE: using these, program will have undefined behavior: although it may
// work now, it might not work when changing compiler.
template<> struct is_binary_compatible< A, B > : public boost::true_type{};
template<> struct is_binary_compatible< B, A > : public boost::true_type{};
输出变为:
$ time ./bnicmop
Allocating Data
Destroying Data
real 0m0.123s
user 0m0.087s
sys 0m0.017s
这应该只用在关键点上(不要偶尔复制一个包含 3 个元素的数组!),并且要使用这个东西,我们至少需要为我们声明的所有类型编写一些(繁重的!)测试单元二进制兼容,以便在我们升级编译器时检查它们是否仍然。
为了更安全,未定义行为的object_static_cast 仅应在设置宏时启用,以便可以在有和没有它的情况下测试应用程序。
关于我的项目,我将在某一点上使用这些东西:我需要将一个大容器转换为另一个容器(它可能与我的容器二进制兼容)我的主循环。