我经常遇到类型为C
的容器T1
(或任何类型的包装类,甚至是智能指针),并希望将此类C<T1>
转换为{ {1}},其中C<T2>
与T2
兼容。
C ++不允许我直接转换整个容器,强制T1
会导致未定义的行为,所以我需要创建一个新的reinterpet_cast
容器并用{{{{}重新填充它1}}项目已投放为C<T2>
。无论是在时间和空间上,这项操作都非常昂贵。
此外,在很多情况下,我非常确定强制C<T1>
可以正常使用任何编译器编译的代码,例如当T2
为reinterpret_cast
时,或者T2
和T1 const
是指针。
是否有一种干净有效的方法来转换T1
中的T2
?
例如C<T1>
运算符(/ function?),当且仅当它与C<T2>
不是二进制兼容时才会创建并重新填充container_cast
?
答案 0 :(得分:4)
此外,在很多情况下,我非常确定强制reinterpret_cast可以正常工作
我打赌你没有。存储不同类型的两个容器永远不会保证二进制兼容,即使它们包含的对象是。即使它们在某些特定版本的某些编译器实现中碰巧是二进制兼容的,这也是一个可以从一个次要版本更改为下一个版本的实现细节。
依靠这种无证的行为为许多令人不愉快的长夜调试打开了大门。
如果要将这些容器传递给函数,只需将该函数作为模板,以便可以将任意类型的容器传递给它。与班级相似。毕竟,这是模板的重点。
答案 1 :(得分:3)
为什么不使用安全的方式
C<T1> c1;
/* Fill c1 */
C<T2> c2(c1.begin(), c1.end());
然后是个人资料。如果它成为瓶颈,那么您可以随时重新访问基础算法,并且可能完全不需要转换。
依赖reinterpret_cast
的任何特定行为现在可能不会引起问题,但从现在开始数月或数年,它几乎肯定会导致某人调试问题。
答案 2 :(得分:3)
除了其他人处理的所有其他问题:
该方法存在一个基本问题,根本不是技术问题。如果苹果是水果,水果容器既不是苹果的容器(通常是展示的),也不是苹果的容器是水果的容器。尝试将西瓜放在一盒苹果中!
如果允许转换容器,那么转到更多技术细节,并专门处理甚至不需要转换的继承(派生对象 已经是基类的对象)如果派生类型为基类型,则可以向容器添加无效元素:
class fruit {};
class apple : public fruit {};
class watermelon : public fruit {};
std::vector<apple*> apples = buy_box_of_apples();
std::vector<fruit*> & fruits = reinterpret_cast< std::vector<fruit*>& >(apples);
fruits.push_back( new watermelon() ); // ouch!!!
最后一行非常正确:您可以向watermelon
添加vector<fruit*>
。但实际效果是您已将watermelon
添加到vector<apple*>
,并且这样做会破坏类型系统。
并非所有看起来都很简单的东西实际上都是理智的。这类似于您无法将int **
转换为const int **
的原因,即使第一个想法是应该允许它。事实是允许这样会破坏语言(在这种情况下是const正确性):
const int a = 5;
int *p = 0;
int **p1 = &p; // perfectly fine
const int **p2 = p1; // should this be allowed??
*p2 = &a; // correct, p2 points to a pointer to a const int
**p1 = 100; // a == 100!!!
这让我们回到你在另一个答案的评论中提供的例子(为了证明一般的观点,我将使用向量而不是集合,因为设置内容是不可变的):
std::vector<int*> v1;
std::vector<const int*> &v2 = v1; // should this be allowed?
const int a = 5;
v2.push_back( &a ); // fine, v2 is a vector of pointers to constant int
// rather not: it IS a vector of pointers to non-const ints!
*v1[0] = 10; // ouch!!! a==10
答案 3 :(得分:2)
您无法转换容器的原因与类型本身无关。问题是你正在尝试构建两个对象,就编译器和链接器而言,它们是两个不相关的类。
例如,当您执行C<int>
和C<short>
时,编译器会发出如下代码:
class C_int_ {
//...
};
class C_short_ {
//...
};
由于这些类显然不相关,因此无法强制转换它们。如果你强迫它(例如,使用C演员),并且它有任何虚拟功能,你可能会爆炸。
相反,您必须使用循环手动完成。遗憾。
答案 4 :(得分:2)
好的,让我总结一下。
你的(正确的!)答案说,在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{};
作为注释sais(如前所述),没有办法实际实现这种类型特征(例如,就像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
,这样就可以在有和没有它的情况下测试应用程序。
关于我的项目,我将在某一点上使用这些东西:我需要将一个大容器转换为另一个容器(可能是二进制兼容与我的一个)我的主循环。
答案 5 :(得分:1)
这通常很难。在考虑模板特化时,问题变得明显,例如臭名昭着的vector<bool>
,其实现与vector<int>
的区别不仅仅是参数类型。
答案 6 :(得分:1)
绝对不能保证这些容器是二进制兼容的,并且可以使用类似reinterpret_cast<>
的内容。
例如,如果容器(如std::vector
)将数据内部存储在C样式数组中,C<T1>
将包含T1[]
数组,而C<T2>
将包含T2[]
数组一个T1
。如果现在T2
和T2
具有不同的大小(例如T1[]
包含更多成员变量),则T2[]
的内存不能简单地解释为C<T1>
因为这些数组的元素将位于不同的位置。
因此,简单地将C<T2>
内存解释为C<T1>
将无效,并且必须进行真正的转换。
(此外,可能存在针对不同类型的模板特化,因此C<T2>
可能看起来与{{1}}完全不同)
要将一个容器转换为另一个容器,请参阅this question或许多其他相关容器。
答案 7 :(得分:0)
这对容器来说确实很困难。类型兼容性不够,类型实际上需要在内存中相同,以防止在分配时切片。有可能实现一个公开兼容类型指针的ptr_container。例如,boost的ptr_containers无论如何都会在内部保留void*
,因此将它们转换为兼容的指针应该可以正常工作。
也就是说,智能指针绝对可以实现。例如,boost::shared_ptr
实现了static_pointer_cast
和dynamic_pointer_cast
。