Developer: Programming
A long time ago, in the dark past, Nigel Thompson wrote a series of technical notes about Ole programming called "Ole dummies. At that time, he was able to hear from the top and bottom of the corridor screaming and forgetting to correctly add or release an interface. I think there should be some ways to automatically release the Component Object Model (COM) component when using the clever pointer in C ++, making it easier to use the component object model components. However, I started to study the Microsoft basic class library (MFC). Referencing computation in this topic is not a focus, because it is hidden in the MFC class.
After writingArticle"MFC/COM Object 8: revisit multiple inheritance without MFC", I decided to use clever pointer classes to simplify the use of the Component Object Model (COM) interface. The results of the study did not fulfill my wish. I began to doubt whether my ownCodeUse clever pointer interface. However, your Component Object Model (COM) project may be different from mine, so I decided not to use the Component Object Model (COM) component may not affect you.
In this article, I will discuss the following topics:
- Why is a smart interface pointer class created?
- How to Create a clever interface pointer class
- Use clever interface pointer class
- Why I don't like clever interface pointer classes
In this articleSource code, I use the prefix pi to indicate a pointer to the interface, for example:
Ipersist * pipersist; I use the prefix Si to refer to a smart interface pointer: csmartinterface
Sipersist;
Reasons for creating a clever interface pointer class
The reason why I want a clever interface pointer class is to automatically add and release the interface pointer.
When using the Component Object Model (COM) interface pointer, you must follow several rules. First, do not call delete on an interface pointer to replace release. The following code is incorrect:
Idraw * pidraw; cocreateinstance (..., IID, _ idraw, (void **) & pidraw); pidraw-> draw (0, 0, 100,100); Delete pidraw; // don't delete an interface pointer. |
The following code is correct:
Idraw * pidraw; cocreateinstance (..., IID, _ idraw, (void **) & pidraw); pidraw-> draw (0, 0, 100,100); pidraw-> release (); |
C ++ProgramThe designer usually deletes an object pointer. For this reason, C ++ programmers are easy to forget and replace release with delete on an interface pointer. It is also a difficult error for C ++ programmers to find, because it is too natural to delete pointers.
The second rule is to call addref when creating a new pointer. The following code is:IncorrectOf:
Idraw * pidraw; cocreateinstance (..., IID, _ idraw, (void **) & pidraw); pidraw-> draw (0, 0, 100,100); Delete pidraw; // don't delete an interface pointer. |
The following code is correct:
Idraw * pidraw; cocreateinstance (..., IID, _ idraw, (void **) & pidraw); pidraw-> draw (0, 0, 100,100); pidraw-> release (); |
The above example is very small, but it is difficult to trace this error in complicated code.
There is not only one reason to use clever interface pointer classes. Donbox mentioned other causes in the C ++ report (see the bibliography at the end of this Article ).
How to Create a clever interface pointer class
The method for creating a clever interface pointer class is the same as that for a clever pointer class: by executing a class operator-". This process can also be called a delegate. By overwriting the operator->, we can make a class to simulate a pointer call. For example:
Void draw () {csmartinterface <idraw> sidraw; cocreateinstance (..., IID, _ idraw, (void **) & sidraw); sidraw-> draw ();} |
Note the following points in the above Code. First, a template class is used to execute clever interface pointers. This makes the clever pointer interface class A security type. Second, as we will see, the operator-has been overloaded to return the address of a pointer included in csmantinterface. Third, even if sidraw is not a pointer, we use the operator-> to call the members in the idraw interface. Fourth, we do not call release because cxmartinterface has been created on the stack, and The Destructor will automatically call release.
The following is an important part of the csmartinterface header file. Therefore, the member functions and operators are defined in the header file later.
Csmartinterface contains a pointer to an interface. For type security, it is defined as a template function. Csmartinterface is essentially an overload operator->.
Template <class I> class csmartinterface {public: // construction csmartinterface (I * Pi = NULL); // copy constructor csmartinterface (const csmartinterface <I> & RSI ); // destruction ~ Csmartinterface (); // assignment from I * csmartinterface & operator = (I * PI); // operators // conversion operator I *(); // deref I * operator-> (); // address of I ** operator & (); // capacity ity bool operator = (I * PI) const; // inequality bool Operator! = (I * PI) const; // negation bool Operator! () Const; protected: I * m_pi ;}; |
Therefore, sidraw-> draw () from the previous example leads to a call to sidraw. m_pi-> draw. Sidraw delegates the draw call to the interface pointed to by M-Pi. This method is powerful in csmartinterface Classes do not need to be changed when each new function is added to the idraw interface. However, as we can see, csmartinterface Individual calls to the idraw interface cannot be stopped.
to make csmartinterface a more credible simulation of pure C ++ pointers, we need to define other operators. In fact, the most difficult thing to do with a clever pointer class is to ensure that all operators used on pointers have been defined and meaningful. For example, when I convert the following code from if (pisimple = NULL) to If (sisimple = NULL )......, I must define the operator = for my clever pointer class. The generated code is not compiled incorrectly. However, it contains an error because it compares null with sisimple instead of the extended sisimple. m_pi. After I defined the operator =, this error disappears. The Directory of your favorite C ++ programming book should list the operators you need to define to act as a convenient query table. For the sake of security, I defined the operators that I think I don't need -- so that if I use a view, they will get an error message. Similar to the article "pointers checked in C ++" by Robert mashlan in the C/C ++ user monthly, this article can really help you understand clever pointers. The vast majority of operators required for overloading make it easy to understand. The most interesting operator is =:
Template <class I> inline csmartinterface <I> & csmartinterface <I >:: operator = (I * PI) {If (m_pi! = PI) // optimize: Same pointer addref/release not needed. {If (m_pi! = NULL) m_pi-> release (); // smart pointers don't use smart pointers :-) m_pi = PI; If (m_pi! = NULL) m_pi-> addref ();} return * This ;} |
Operator = automatically adds and releases interfaces for you. If the csmartinterface already points to an interface, it will release it and add a new interface. The = Operator allows the following operations:
Void drawthemall () {csmartinterface <idraw> sidraw; For (INT I = 0; I <Max; I ++) {sidraw = pidraw [I]; sidraw-> forward (x );}} |
The code above relies on the destructor of csmartinterface to release pointers:
Template <class I> inline csmartinterface <I> ::~ Csmartinterface () {If (m_pi! = NULL) {m_pi-> release ();}} |
We can use the appropriate constructor to make better use of the destructor.
Template <class I> inlinecsmartinterface <I>: csmartinterface (I * PI/* = NULL */): m_pi (PI) {If (m_pi! = NULL) {// addref if we are copying an existing interface pointer. m_pi-> addref ();}} |
Now our example can be changed:
Void drawthemall () {for (INT I = 0; I <Max; I ++) {csmartinterface <idraw> sidraw (pidraw [I]); sidraw-> forward (x );}} |
The above code runs a list of idraw interface pointers, add them, use them, and release them.
You can also do more about this. In his topic, donbox elaborated on this point more deeply. He defines the csmartinterface's equivalents to accept the interface IDs and types. Then he defines a constructor. Once a value is assigned from different interfaces, this interface function will call the query interface:
Csmartinterface (iunknown * PI): m_pi (null) {If (Pi! = NULL) Pi-> QueryInterface (* piid, (void **) & m_pi );} |
The above constructor allows us to change the example:
Void drawthemall () {for (INT I = 0; I <idraw, & iid_draw> MAX; I ++) {csmartinterface sidraw (piunknown [I]); sidraw-> forward (x );}} |
This example shows the strength of this technology. The above code is similar:
Void drawthemall () {idraw * pidraw; For (INT I = 0; I <Max; I ++) {piunknown [I]-> QueryInterface (iid_draw, (void **) & pidraw); pidraw-> forward (x); pidraw-> release ();}} |
The operator = can also be extended in the same way, so a value assignment like sidraw = piunknown will call QueryInterface. I am not keen on hiding execution behind seemingly harmless operators, although I have to say that using this method to overload operator = is a very convincing way. In Visual Basic 4.0, QueryInterface is called when a component object model is assigned to another component.
Use clever interface pointer class
Csmartinterface has two main rules. First, do not call release on the csmartinterface object.
Void draw () {csmartinterface <idraw> sidraw; cocreateinstance (..., IID, _ idraw, (void **) & sidraw); sidraw-> draw (); sidraw-> release (); // will compile, but is a bug .} |
When sidraw-> release is called, sidraw-> m_pl is released. When the destructor of sidraw-> m_pi is called, sidraw-> m_pi will be released again as long as the interface is not released. This issue is not difficult to debug. If you have any questions about reference counting, you can find all the release events that occur. Another approach is to apply # define release bogus_do_not_call_release! However, using release in this way produces an error. Of course, if your application contains other functions with the word "release" (like many Win32 application interfaces [APIs], this method will not work.
The second rule is to avoid using a csmartinterface object as a pointer. If you do this, the situation will be confusing:
Csmartinterface <isimple> * psisimple = new csmartinterface <isimple> (m_pisimple); (* psisimple)-> Inc (); Delete psisimple; |
You can use typedef to sort this situation a little bit:
Typedef csmartinterface <isimple> csmartisimple; csmartisimple * psisimple = new csmartisimple (m_pisimple); (* psisimple)-> Inc (); Delete psisimple; |
However, this does not change (* psisimple)-> INCC); in fact, it is not very simple.
If we want a pointer to an interface, we may find a path around the problem. We want to release the interface at any point in the program -- instead of waiting until the csmartinterface is out of the scope. The problem is how to release interfaces contained in csmartinterface. The answer is very simple: sisimple = NULL; although this is a typical C code, what happened here is not obvious at all.
Pisimple-> release (); anddelete psisimple; |
Is a more obvious way to indicate that the object has disappeared.
Why I don't like clever interface pointer classes
There are several reasons why I don't want to use clever pointer classes. The main point of all these reasons is that csmartinterface is not like C ++. It is really strange to use the operator> rather than the pointer to an object on an object.
One related reason is that using a pointer to csmartinterface is not straightforward-in fact it is very messy. Most of my component object models use containers with hidden interface pointers, and the interface lifecycle is rarely restricted within the scope of functions. I call QueryInterface for an interface, store it in the container, use it, and finally release it -- so these are all from different places in my code. Using this structure type, I need to allocate and release clever interfaces on the stack, which is as messy as I described in the previous chapter.
A c ++ programmer may have to delete an interface pointer, while a Component Object Model (COM) programmer may have to release a csmartinterface object. There is no convenient way to block this. Therefore, our solution has replaced a problem with a similar equality problem. Of course, at least so far, the number of C ++ programmers is more than that of the Component Object Model (COM) programmers.
I have decided to use interface packaging instead of clever interface pointers. The interface packaging is described in my article "calling Component Object Model (COM) object through interface packaging.
Summary
clever interface pointer is a powerful technique that makes it easier to work with Component Object Model (COM) objects and more bug_free. However, I found that clever interface pointers are a very strange thing. They are neither pure pointers nor pure objects. Nor can they fit my application structure as I expected. However, I strongly recommend that you try using clever interface pointers in your application to see how they work for you. They may be just the skills you need to make your application faster and with fewer problems.