假设有一个类A,里面有一个CImage。如果用vector储存,erase前面的元素后,后面的CImage就无法使用,会报ATLASSERT( hBitmap == m_hBitmap );
检查失败
struct A
{
CImage img;
};
int _tmain(int argc, _TCHAR* argv[])
{
vector<A> imgs(2);
imgs[0].img.Create(1, 1, 32);
imgs[1].img.Create(1, 1, 32);
// 正常工作
imgs[0].img.GetDC();
imgs[0].img.ReleaseDC();
imgs[1].img.GetDC();
imgs[1].img.ReleaseDC();
imgs.erase(imgs.begin()); // 删除第一个img
imgs[0].img.GetDC();
imgs[0].img.ReleaseDC(); // 出错,ATLASSERT( hBitmap == m_hBitmap );检查失败
return 0;
}
GetDC和ReleaseDC是这样的
inline HDC CImage::GetDC() const throw()
{
ATLASSUME( m_hBitmap != NULL );
m_nDCRefCount++;
if( m_hDC == NULL )
{
m_hDC = GetCDCCacheInstance()->GetDC();
m_hOldBitmap = HBITMAP( ::SelectObject( m_hDC, m_hBitmap ) );
}
return( m_hDC );
}
inline void CImage::ReleaseDC() const throw()
{
HBITMAP hBitmap;
ATLASSUME( m_hDC != NULL );
m_nDCRefCount--;
if( m_nDCRefCount == 0 )
{
hBitmap = HBITMAP( ::SelectObject( m_hDC, m_hOldBitmap ) );
ATLASSERT( hBitmap == m_hBitmap );
GetCDCCacheInstance()->ReleaseDC( m_hDC );
m_hDC = NULL;
}
}
调试看了一下,ReleaseDC里的SelectObject返回值是NULL,更神奇的是上一句的GetDC里的SelectObject返回值也是NULL。看了一下SelectObject的文档,当调用失败时才返回NULL,而且一张BITMAP不能选入多个DC中。不过这里的BITMAP也没有选入多个DC啊,想了半天也没想出什么错误,直到我偶然调试到CImage的析构函数时发现,vector中的两个img的hBitmap居然是一样的
仔细想想,应该是vector删除前面元素时需要把后面的元素往前移动,然后不小心把后面的元素拷贝了一份,然后在后面元素的析构函数中把hBitmap释放了。查看erase的实现证实了我的想法
// TEMPLATE FUNCTION move
template<class _InIt,
class _OutIt> inline
_OutIt _Move(_InIt _First, _InIt _Last,
_OutIt _Dest, _Nonscalar_ptr_iterator_tag)
{ // move [_First, _Last) to [_Dest, ...), arbitrary iterators
for (; _First != _Last; ++_Dest, ++_First)
*_Dest = _STD move(*_First); // 由于CImage没有实现移动的=操作符,这里调用了默认的拷贝的=操作符
return (_Dest);
}
解决方法:
-
自己实现移动的=操作符
struct A
{
A& operator= (A&& other)
{
img.Destroy();
img.Attach(other.img.Detach());
return *this;
}
CImage img;
};
-
用指针管理CImage
int _tmain(int argc, _TCHAR* argv[])
{
vector<unique_ptr<A> > imgs;
imgs.push_back(make_unique<A>());
imgs.push_back(make_unique<A>());
imgs[0]->img.Create(1, 1, 32);
imgs[1]->img.Create(1, 1, 32);
// 正常工作
imgs.erase(imgs.begin());
imgs[0]->img.GetDC();
imgs[0]->img.ReleaseDC();
return 0;
}