我有一个类模板,我用来“包装”几种常见类型的容器。目标是取代
getItems( vector< ItemType > &x );
getItems( set< ItemType > &x );
getItems( my_custom_vector< ItemType > &x );
(...and several others...)
使用单个函数,如下所示:
getItems( Cont< ItemType > x );
一个精简的实现(我只包含向量代码)目前看起来像这样:
template< class T > ContBase {
public:
ContBase( void *container ) : mContainer( container ) {}
virtual void add( const T& item ) = 0;
virtual void clear() = 0;
protected:
void *mContainer;
};
template< class T > ContVector : public ContBase< T > {
public:
ContVector( std::vector< T > &native_container )
: ContBase( (void *) &native_container ) {}
void add( const T& item ) { vec().push_back(item); }
void clear() { vec().clear(); }
protected:
std::vector< T > &vec() { return *(std::vector< T > *) mContainer; }
};
template< class T > class Cont {
public:
Cont( std::vector< T > &x ) { new (&mMem) ContVector< T >( x ); }
void clear() { container()->clear(); }
void add( const T& item ) { container()->add( item ); }
protected:
ContBase< T > *container() { return (ContBase< T > *) &mMem[ 0 ]; }
unsigned char mMem[ sizeof(ContBase< T >) ];
};
我做了一些初步测试,它在受控的基准测试设置中运行良好。我认为,这里主要的棘手问题是聪明在Cont的构造函数中使用了新的贴图。但是,一旦我把它放在一个真正的应用程序(Visual Studio 2010 C ++编译器)中并运行整个程序优化(带链接时代码生成,优化速度等),我就开始看到严重的问题。具体来说,我有这样的功能:
void MyClass::myFunc( Index x, Cont< Index > container, uint r )
{
// ... Return if x is invalid
cerr << "VFPTR: " << (void*) ((uint *) ((void*) &verts))[0] << endl;
container.add( x );
// ...Potentially add some more things to container.
}
这给了我这样的输出(请注意,疯狂的类型转换是在VC ++中获取虚函数表的值的一个hack):
VFPTR: 0AD98708
// Repeat above line 17 times
VFPTR: 0018B830
首先,确实虚拟函数指针可以不同似乎很奇怪。其次,令人讨厌的是,这个相同的代码在调试模式下工作;这仅在启用优化时显示。第三,值得注意的是,新指针(0018B830)是指向 base 类ContBase中的虚函数表的指针。当您调用container.add(x)时,会发生崩溃,这是一个NULL值。
所以我的核心问题是:这是合法代码,还是我错过了一个未定义的行为案例? Microsoft的编译器在这里被破坏了,还是我的代码未定义为开始?
答案 0 :(得分:0)
我看到的主要未定义行为问题是Cont<T>
的(默认)复制构造函数和赋值运算符执行mMem数组中保存的ContVector<T>
对象的逐字节副本。由于ContVector<T>
不是可复制的(参见C ++规范中的9和3.9),因此未定义。根据您对确切问题的描述,似乎优化器假设它可以使用mMem数组的字节副本重新排序某些操作,这会导致您获得部分构造(或部分破坏)对象的副本。 / p>
所以你可以通过添加一个复制ctor和赋值运算符来正常复制/分配而不是依赖于mMem数组的逐字副本来完成这项工作。