引言
工作项目中遇到过一个问题,代码中某个函数导致内存泄漏,而且泄漏随此函数调用次数变化而变,当调用次数较少时,泄漏并不明显,而当该函数调用次数超过千次后,内存泄漏就十分明显。
具体代码如下
HRESULT GetWhatYouWantInLocal(ULONG i_uIdx,IRoHaHa** o_ppComPosture)
{
std::vector<IZYouWantPtr> vecRoElem;
GetYouWant(vecRoElem);
ULONG uCnt = vecRoElem.size();
if (i_uIdx >= uCnt || uCnt == 0)
{
return E_FAIL;
}
……
return S_OK;
}
当时百思不得其解,找了领导一看,立马指出下面两句就是根源所在。
std::vector<IZYouWantPtr> vecRoElem;
GetYouWant(vecRoElem);
即STL容器不能与COM智能指针共用。
原因
原因:重载operator&的行为破坏了CopyConstructible
具体原因,经度娘和谷哥之后,有如下说法:
- Effective STL中明确说了STL不支持智能指针,因为智能指针是不支持拷贝构造和可赋值的。
- vector中元素的两个要求是:1.元素必须能赋值2.元素必须能复制 。
- 按照C++标准,可以存储在任何容器中的对象都必须有CopyConstructible。
- 从另外一个角度解释的:智能指针为了保证自己的智能性,没有定义一个参数为const auto_ptr& 的拷贝构造函数。但是vector在声明的时候,就要用这样的拷贝构造函数。
- 忠告:不建议将对象作为容器的元素, 一般都是存放对象的指针, 而且不建议智能指针和容器结合使用。 因为智能指针有引用计数的 比如bstr_t, CComPtr, 还有赋值转移拥有权的CAutoPtr。 这些情况比较复杂, 所以容器还是用裸指针简单也安全。
由于时间紧、任务重,就没有空细究了,下面摘一些个人认为讲的不错的片段如下。
按照C++标准,可以存储在任何容器中的对象都必须有CopyConstructible。
CopyConstructible的一个要求就是,假设t是存储在容器中的一个类型为T的对象,那么&t就返回T*,也就是t的地址。而ATL的智能包裹类,如CComBSTr、CCOMPtr等,却都重载了操作符&,CComBSTR类的&操作符返回CComBSTR::m_str,这是一个BSTR类型。这样就破坏了CopyConstructible的要求。
原文中举了一个栗子:std::list< CComBSTR > list;那么MSVC6.0(SP5)就会产生一个编译错误:f:\program files\microsoft visual studio\vc98\include\list(238) : error C2664: ‘destroy’ :cannot convert parameter 1 from 'unsigned short ** ’ to ‘class ATL::CComBSTR *’
但VS2015下实测并没有报错。
CComBSTR
BSTR* operator&() throw()
{
#ifndef ATL_NO_CCOMBSTR_ADDRESS_OF_ASSERT
ATLASSERT(!*this);
#endif
return &m_str;
}
CComPtr
T** operator&() throw()
{
ATLASSERT(p==NULL);
return &p;
}
说到这里,我们就不难理解为什么“error C2664: ‘destroy’ : cannot convert parameter 1 from 'unsigned short ** ’ to ‘class ATL::CComBSTR *’”了。
这种重载了&操作符的行为,在与STL联合使用时,会导致各种各样的问题(从内存泄漏到随机崩溃)。
这些危险对象包括有:CComPtr、CComQIPtr、CComBSTR、_com_ptr_t。千万不要把它们放入任何STL容器中。当然也要留意其他重载了operator&的类。
解决办法
使用CAdapt模板类ATL为我们提供了解决办法:CAdapt模板类。这个模板重载了&操作符,不再让它返回对象的地址。
template <class T>
class CAdapt
{
public:
CAdapt()
{
}
operator T&()
{
return m_T;
}
operator const T&() const
{
return m_T;
}
……
T m_T;
};
实际上使用下面的代码似乎也没有出现问题,比如内存泄漏等:std::vector vec;vec.push_back(CComBSTR(“string”))。
对于这种情况,Igor Tandetnik是这么说的:“有时候,你在STL Container中没有用CAdapt,看上去平安无事。但是,这样的话,你的代码就和STL厂商的具体实现密切相关了。从而当你调用以前从没有用过的容器的某一个方法时,可能会发生一些未定义的事情。所以,Just to be on the safe side,无论何时,当你要把CComBSTR或者其他重载了operator&的类放入任何STL容器中时,请使用CAdapt。”
主要参考链接: https://www.cnblogs.com/Clingingboy/archive/2011/06/13/2080077.html
1:https://blog.csdn.net/Virtual_Func/article/details/49724135
2:https://bbs.csdn.net/topics/330192997/
3:https://blog.csdn.net/bearcoding/article/details/45893813