Sort function of STL Gives Unexpected Output when Vector is a Type Derived from _com_ptr_t
C++ code which contains an STL vector containing types derived from the_com_ptr_t COM smart pointer class and using the std:sort() function to sort interface pointers may give unexpected sort output._com_ptr_t COM smart pointer derived types are commonly created when using the '#import' keyword in C++ to import a COM type library. For example:
#import "msxml4.dll"
The _com_ptr_t class implementation overrides the reference (&) operator and returns a null interface pointer. This is in conflict with STL reference operator rules and may result in incorrect sorting order when std::sort () is called.
Sample Code:
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>
// Create smart pointer wrappers for msxml com object
#import "msxml4.dll"
// Helper Function to create XML
_bstr_t CreateXML()
{
int nums[] = {1,10,11,12,13,14,15,16,17,18,19,2,20,21,22,23,24,25,26,27,28,29,3,30,31,32,33,34,4,5,6,7,8,9};
std::stringstream tempstr;
tempstr << "<NodesCol>" << std::endl;
for(int i=0; i<34 ;i++)
tempstr << "\t<Node><NodeNum>" << nums[i] << "</NodeNum></Node>" << std::endl;
tempstr << "</NodesCol>" << std::endl;
return static_cast<_bstr_t>(tempstr.str().c_str());
}
// Compare Function for std::swap
bool myCompare(MSXML2::IXMLDOMNodePtr& node1, MSXML2::IXMLDOMNodePtr& node2)
{
int nNode1No(-1),nNode2No(-1);
if( ( NULL == node1.GetInterfacePtr() ) || ( NULL == node2.GetInterfacePtr() ) )
return false;
_stscanf_s( node1->text, _T("%d"), &nNode1No );
_stscanf_s( node2->text, _T("%d"), &nNode2No );
if( nNode1No < nNode2No )
return true;
return false;
};
int _tmain(int argc, _TCHAR* argv[])
{
// A vector of IXMLDOMNode smart pointers
std::vector<MSXML2::IXMLDOMNodePtr> vecNodesList;
CoInitialize( NULL );
// Create and Load the XML document
MSXML2::IXMLDOMDocumentPtr docPtr;
docPtr.CreateInstance(__uuidof(MSXML2::DOMDocument40));
docPtr->loadXML(CreateXML());
MSXML2::IXMLDOMNodeListPtr nodes = docPtr->selectNodes( _T("NodesCol/Node/NodeNum") );
std::cout << "Before Sorting Nodes" << std::endl;
// Initialize the vector of smart pointers
for( int i = 0; i < nodes->length; i++ )
{
int nNodeNo = -1;
vecNodesList.push_back( nodes->item[i] );
std::cout << nodes->item[i]->text << std::endl;
}
// Sort the vector of smart pointers
std::sort( vecNodesList.begin(), vecNodesList.end(), myCompare );
// Display sorted results
std::cout << "\nAfter Sorting Nodes\n" << std::endl;
std::vector<MSXML2::IXMLDOMNodePtr>::iterator iter;
for(iter = vecNodesList.begin(); iter!=vecNodesList.end();iter++)
{
if(NULL != (*iter).GetInterfacePtr())
std::cout << (*iter)->text << std::endl;
else
std::cout << "-1" << std::endl;
*iter = NULL; // Release smart pointer before destroying vector;
}
// Release smart pointers before CoUninitialize
nodes = NULL;
docPtr = NULL;
CoUninitialize( );
return 0;
}
The sort() method uses the utility function std::swap():
template<class _Ty> inline
void swap(_Ty& _Left, _Ty& _Right)
{
// exchange values stored at _Left and _Right
if (&_Left != &_Right)
{
// different, worth swapping
_Ty _Tmp = _Left;
_Left = _Right;
_Right = _Tmp;
}
}
The "if (&_Left != &_Right)" line of code calls the overloaded operator & from _com_ptr_t comip.h header file, which returns a NULL interface pointer:
Interface** operator&() throw()
{
_Release();
m_pInterface = NULL;
return &m_pInterface;
}
This is a known issue.
All STL containers, including vector, forbid their elements from overloading operator&(). (This is C++ Specification 03 23.1/3 "The type of objects stored in these components must meet the requirements of CopyConstructible types (20.1.3) and the additional requirements of Assignable types", and 20.1.3/1 requires &t to have type T * and denote the address of t.)
The best solution is to never overload operator&() when using STL containers. If that is not possible (e.g. because the operator overload is coming from code that you don't control), an alternative is to wrap the class in another wrapper class that doesn't overload operator&() which you can then add to the vector or other container class. CAdapt is one example of such a class.
Fixed Sample Code example using CAdapt
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>
#include "atlcomcli.h"
// Create smart pointer wrappers for msxml com object
#import "msxml4.dll"
// Helper Function to create XML
_bstr_t CreateXML()
{
int nums[] = {1,10,11,12,13,14,15,16,17,18,19,2,20,21,22,23,24,25,26,27,28,29,3,30,31,32,33,34,4,5,6,7,8,9};
std::stringstream tempstr;
tempstr << "<NodesCol>" << std::endl;
for(int i=0; i<34 ;i++)
tempstr << "\t<Node><NodeNum>" << nums[i] << "</NodeNum></Node>" << std::endl;
tempstr << "</NodesCol>" << std::endl;
return static_cast<_bstr_t>(tempstr.str().c_str());
}
// Compare Function for std::swap
bool myCompare(MSXML2::IXMLDOMNodePtr& node1, MSXML2::IXMLDOMNodePtr& node2)
{
int nNode1No(-1),nNode2No(-1);
if( ( NULL == node1.GetInterfacePtr() ) || ( NULL == node2.GetInterfacePtr() ) )
return false;
_stscanf_s( node1->text, _T("%d"), &nNode1No );
_stscanf_s( node2->text, _T("%d"), &nNode2No );
if( nNode1No < nNode2No )
return true;
return false;
};
int _tmain(int argc, _TCHAR* argv[])
{
// A vector of IXMLDOMNode smart pointers
std::vector<CAdapt<MSXML2::IXMLDOMNodePtr>> vecNodesList;
CoInitialize( NULL );
// Create and Load the XML document
MSXML2::IXMLDOMDocumentPtr docPtr;
docPtr.CreateInstance(__uuidof(MSXML2::DOMDocument40));
docPtr->loadXML(CreateXML());
MSXML2::IXMLDOMNodeListPtr nodes = docPtr->selectNodes( _T("NodesCol/Node/NodeNum") );
std::cout << "Before Sorting Nodes" << std::endl;
// Initialize the vector of smart pointers
for( int i = 0; i < nodes->length; i++ )
{
int nNodeNo = -1;
vecNodesList.push_back( nodes->item[i] );
std::cout << nodes->item[i]->text << std::endl;
}
// Sort the vector of smart pointers
std::sort( vecNodesList.begin(), vecNodesList.end(), myCompare );
// Display sorted results
std::cout << "\nAfter Sorting Nodes\n" << std::endl;
std::vector<CAdapt<MSXML2::IXMLDOMNodePtr>>::iterator iter;
for(iter = vecNodesList.begin(); iter!=vecNodesList.end();iter++)
{
if(NULL != (*iter).m_T.GetInterfacePtr())
std::cout << (*iter).m_T->text << std::endl;
else
std::cout << "-1" << std::endl;
*iter = NULL; // Release smart pointer before destroying vector;
}
// Release smart pointers before CoUninitialize
nodes = NULL;
docPtr = NULL;
CoUninitialize( );
return 0;
}
Jyoti Patel
Developer Support VC++ and C#