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#