Introduction
This article describes an easy method to reduce compilation dependencies and build times. This method would not be interesting enough to warrant an article on it, except that it is the key to an interesting method of managing project-wide objects, and I have not seen this method mentioned anywhere.
Background
While watching another religious war starting to erupt over the use of Singletons, I realized that I had not seen the method I use discussed anywhere else, including CP. It has some advantages to it, and, as I haven't seen it mentioned, I figured I'd post it. (Of course, having said that, I'll probably come across an article on the exact same thing tomorrow.)
The method
The method itself, as I said, is not very interesting. Simply use smart pointers to hold all non-POD objects stored in your class interface.
Well, there is one interesting thing about it - you cannot use auto_ptr's or many other smart pointers in order to store your objects if you don't include at least an empty destructor inside the unit's .cpp file. That is the reason the following uses Alan Griffith's grin_ptr. You could also use boost::shared_ptr, and probably some others that I am unaware of.
The reason that you can't use an auto_ptr for this purpose is simply that auto_ptr must have a complete definition of the forward declared class at the point of destruction, and if you rely upon the default destructor, the forward declaration of the class is the only thing the auto_ptr has, so it will generate a 'do-nothing' default destructor for the class being held. This is rarely, if ever, what you want.
(If you are unsure whether a smart pointer can be used for this purpose without creating a destructor in the holding class' .cpp file, look in the smart pointer's documentation for a statement saying something to the effect that it can hold and correctly destroy an incomplete type. If it says that, you can safely use it without having to remember to supply a destructor in the .cpp file.)
As a quick example of the pattern I am talking about, here is a theoretical implementation of my wallet:
#include "arg.h"
class CreditCard;
class BusinessCard;
class DollarBill;
class Wallet {
private:
arg::grin_ptr<CreditCard> masterCardC;
arg::grin_ptr<BusinessCard> businessCardC;
arg::grin_ptr<DollarBill> dollarBillC;
public:
Wallet();
BusinessCard & businessCard()
{ return *businessCardC.get(); }
CreditCard & masterCard()
{ return *masterCardC.get(); }
DollarBill & dollarBill()
{ return *dollarBillC.get(); }
};
#include "Wallet.h"
#include "CreditCard.h"
#include "BusinessCard.h"
#include "DollarBill.h"
Wallet::Wallet() :
masterCardC(new MasterCard()),
businessCardC(new BusinessCard()),
dollarBillC(new DollarBill()) {
}
(Feel free to 'new' me some more dollar bills. :) )
Anyway, as you can see, nothing to get excited over, until you think about it for a second. We have just entirely eliminated all external dependencies except the 'arg.h' file from the Wallet header. If the implementation to CreditCard, BusinessCard, or DollarBill changes, the only units that need to be recompiled in the project are the Wallet unit and the unit that you changed. This is a big savings over having 'hard objects' in the class' header file. In that case, every unit that #included Wallet.h would be recompiled anytime the implementation to CreditCard, BusinessCard, or DollarBill changed.
The savings with the above method is not as good as a full pimpl implementation, as a full pimpl implementation enables you to recompile CreditCard, BusinessCard, or DollarBill, without the Wallet unit or any other unit in the project needing to be recompiled. (Of course, changing the interface to a pimpl can be a PITA, and will require more to be recompiled at that time.)
The method I have just outlined is simpler than the pimpl pattern, as it does not require you to create an intermediary 'PimplHolder' class. You do, however, have to use '->' notation to access all of the smart pointer held objects from within the class they are held in, unless you create a local reference to them within the function using them.
The 'interesting use'
The above method can be used to easily manage project-wide objects. This method, when used in a global, can quite often be used as a quasi-Singleton manager. Doing so will often simplify some aspects of your overall design, and it will do so without increasing compilation dependencies.
Please do not take this to mean that I don't like Singletons. The pattern I am about to show you does not replace Singletons. There is nothing in this pattern to keep you from instantiating multiple copies of the objects held in this container. This pattern simply makes it very easy to manage and use project-wide objects, and it may be an appealing alternative if you do not really care to mess with figuring your way around Singleton instantiation order dependencies.
Also, do not take this to mean that I am a huge proponent of globals. This pattern allows me to minimize the use of globals to two or three for my entire project, and I am happy with that. I do store quite a few variables within the global objects, though, and as these variables are defined within the header file of the global, whenever their interface changes, or I add another item to the global, every unit that #includes Globals.h will be recompiled at that time. If your project takes considerable time for a rebuild of such a nature, you will want to carefully atomize your globals, maybe even to the point of making each object (like 'TextureManager' in the following code) into its own global item. I outline a wrapper that will simplify this for you in the addendum at the end of this article.
Let me give a simple example of this 'interesting use'. The two changes to the previous example that are needed are to change it so that it holds things commonly held in Singletons, and make the class into a global. The example I gave while the religious war was raging was the following:
#include "arg.h"
class TextureManager;
class LoggingSystem;
class ObjectManager;
class Globals {
private:
arg::grin_ptr<TextureManager> textureManagerC;
arg::grin_ptr<LoggingSystem> loggerC;
arg::grin_ptr<ObjectManager> objectManagerC;
public:
Globals();
TextureManager & textureManager()
{ return *textureManagerC.get(); }
LoggingSystem & logger()
{ return *loggerC.get(); }
ObjectManager & objectManager()
{ return *objectManagerC.get(); }
};
#include "TextureManager.h"
#include "LoggingSystem.h"
#include "ObjectManager.h"
Globals::Globals() :
textureManagerC(new TextureManager()),
loggerC(new LoggingSystem()),
objectManagerC(new ObjectManager()) {
}
#include "Globals.h"
Globals gGlobals;
#include "TextureManager.h"
int main() {
gGlobals.textureManager().doSomething();
return 0;
}
And that is it, although if you need to pass in initialization parameters, in order to pass them to one of your classes, you will need to implement gGlobals as a pointer, and initialize it after whatever parameter it is dependant upon is obtained. The best option is to implement it through the use of an auto_ptr (in which case auto_ptr has no problems):
Globals * gGlobals;
int main() {
std::auto_ptr<Globals> tGlobals(new Globals());
gGlobals = tGlobals.get();
Using the method outlined above, you can explicitly control your object creation order, and very easily overcome the issues that arise when trying to control multiple Singletons with inter-singleton creation order dependencies. In addition, this method has a simpler syntax than Singletons. Singletons require something like SingletonManager::getInstance().textureManager().doSomething() in order to use them from a Singleton manager. The above method boils down to gGlobals.textureManager().doSomething().
But the truly interesting part is that using this technique, if you only modify the TextureManager.cpp file, it will be the only file recompiled at recompilation. If you modify the TextureManager.h file, only units that explicitly #include "TextureManager.h" will be recompiled. This will include the Globals unit, but will not include every file that #includes "Globals.h".
It is worth reading the last paragraph again, and looking at the code, until you understand that this system is not exposing any of the other objects being managed by the Globals unit to any unit that is not #includeing the sub-unit you wish access to. You can #include "Globals.h" in every .cpp file in your program, but they won't link to TextureManager until you explicitly #include "TextureManager.h" as well as Globals.h in the unit you want to access the TextureManager from. There are no other compilation dependencies to be aware of, and the Globals unit does not impose any more overhead than a few forward declarations, the class declaration of Globals itself, and a few bits for the grin_ptr's internals.
The secrets to this whole technique: using only forward declarations and a capable smart pointer.
I hope that you find this technique useful, and wish you happy coding.
If you do atomize your globals, and do not wish to use Singletons, you can modify the previous method to instantiate your globals within main, and completely control your instantiation and destruction order:
Globals * gGlobals;
TextureManager * gTextureMan;
int main() {
std::auto_ptr<Globals> tGlobals(new Globals());
gGlobals = tGlobals.get();
std::auto_ptr<TextureManager> tTextureMan(new TextureManager());
gTextureMan = tTextureMan.get();
You could even create a class to manage these atomized globals. Doing so would overcome the previous objection to long build times. I envision something of the following nature:
class TextureManager;
class OtherGlobals;
class GlobalManager {
private:
arg::grin_ptr<TextureManager> textureManagerC;
arg::grin_ptr<OtherGlobals> otherGlobalsC;
};
#include "TextureManager.h"
TextureManager * gTextureMan;
#include "OtherGlobals.h"
OtherGlobals * gOtherGlobals;
GlobalManager::GlobalManager() {
textureManagerC.reset(new TextureManager());
gTextureMan = textureManagerC.get();
otherGlobalsC.reset(new OtherGlobals());
gOtherGlobals = otherGlobalsC.get();
}
#include "GlobalManager.h"
int main() {
std::auto_ptr<GlobalManager> globals(new GlobalManager());
Using this method, all of your globals will automatically be instantiated for you in a manner that only forces your main unit to recompile if you add more globals. You no longer have the 'hiding' that took place in the earlier pattern I discussed, but you have a simple method of controlling your global class instantiation order. You can even explicitly control the destruction order, if you desire, by creating a destructor for the global organizer class, and calling 'release' upon the smart pointers in the order you want the objects to be released.
Hopefully, the above discussion has given you more options when it comes to implementing global objects. As always, use what works for you, and keep the religious wars to a minimum :)
History
- Oct 11th, 2005 - Added addendum outlining another global organizer method.
|
|
 |
 | impl_ptr by Vladimir Batov Snakefoot | 23:17 22 Aug '08 |
|
 |
Hi ya
Think you should mention there is a big difference between boost smart_ptr and the grin_ptr. Because the grin_ptr ensures deep copy while smart_ptr only performs reference counted shallow copy. Seen lots of places where people say one can use the smart_ptr for the pImpl idiom, but it will only implement automatic deletion and not the right copy-constructor and assignment-operator (Necessary if the pImpl includes member-variables).
Vladimir Batov have created smart pointer called impl_ptr, that is similar to the grin_ptr. It ensures proper calling of copy-constructor and assign-operator (grin_ptr uses copy-constructor in both cases). It also supports NULL pointers.
See the article C++ - Making Pimpl Easy by Vladimir Batov.
The article describes a rather large frame-work, but I have just "stolen" the impl_ptr which works fine by itself. I have modified it a little to support common smart pointer methods, and also changed the traits system, because it fails to work in epilog code.
#pragma once
#include "boost/shared_ptr.hpp"
template"">
class impl_ptr { public: ~impl_ptr () { traits_->destroy(p_); } impl_ptr () : traits_(new deep_copy()), p_(0) {} impl_ptr (T* p) : traits_(new deep_copy()), p_(p) {} impl_ptr (impl_ptr const& that) : traits_(that.traits_),p_(traits_->copy(that.p_)) {} impl_ptr& operator=(impl_ptr const& that) { traits_->assign(p_,that.p_); return *this; }
void swap(impl_ptr& that) { std::swap(p_, that.p_), std::swap(traits_, that.traits_); }
T* get() const { return p_; }
T & operator*() const { BOOST_ASSERT(p_ != 0); return *p_; }
T * operator->() const { BOOST_ASSERT(p_ != 0); return p_; }
void reset(T* p) { impl_ptr(p).swap(*this); }
private: struct traits { virtual ~traits() {} virtual void destroy (T*&) const =0; virtual T* copy (T const*) const =0; virtual void assign (T*&, T const*) const =0; }; struct deep_copy : public traits { virtual void destroy (T*& p) const { boost::checked_delete(p); p= 0; } virtual T* copy (T const* p) const { return p ? new T(*p) : 0; } virtual void assign (T*& a, T const* b) const
{ if ( a == b); else if ( a && b) *a = *b; else if (!a && b) a = copy(b); else if ( a && !b) destroy(a); } };
boost::shared_ptr traits_; T* p_; };
|
|
|
|
 |
 | grin_ptr vs. clone_ptr unjedai | 12:03 28 Nov '07 |
|
 |
I'm wondering how grin_ptr and clone_ptr compare.
grind_ptr: (http://www.octopull.demon.co.uk/arglib/TheGrin.html) clone_ptr: (http://www.codeproject.com/vcpp/stl/clone_ptr.asp)
|
|
|
|
 |
 | How to make Wallet strongly exception safe? ~MyXa~ | 23:09 24 Oct '05 |
|
|
 |
|
 |
Wallet is exception safe. If the 'DollarBill' ctor throws an exception, the BusinessCard and CreditCard objects will be released properly, due to C++'s scoping rules, and the fact that the grin_ptr's dtor will be called.
As it is written, therefore, I believe it meets the strong exception safety guarantee. But as you add methods to Wallet, each method would have to be designed independently in order to continue to meet the strong exception guarantee.
David
(and since it is you I am responding to, I'll respond to another thread of yours from a long time ago )
Debugging - The high art and magic of cussing errors into 'features'
|
|
|
|
 |
|
|
 |
|
 |
Yes, I am David. That account would not allow me to post in the lounge, so I created this one.
To your question. I never use copy assignment, believe it or not, so it took a little reading of that article to get a feel for it. According to that, all that is necessary is to provide the copy assignment operator done up properly. As the grin_ptr has a non-throwing swap, I believe the following should do it. As my first statement in this paragraph insinuates, this is not one of my strong points, so I may have missed something. If you see something I missed, let me know.
David
(ps - you can download the arg library at: http://www.octopull.demon.co.uk/download/arg.zip)
#include
#include "arg_grin_ptr.h"
class CreditCard { private: int cardNumberC; public: CreditCard(int number) : cardNumberC(number) { } int cardNumber() const { return cardNumberC; } };
class BusinessCard { private: std::string nameC; public: BusinessCard(std::string str) : nameC(str) { } std::string name() const { return nameC; } };
class Money { private: int denominationC; public: Money(int denomination) : denominationC(denomination) { } int denomination() const { return denominationC; } };
class Wallet { private: arg::grin_ptr creditCardC; arg::grin_ptr businessCardC; arg::grin_ptr moneyC; public: Wallet(int ccNum, std::string bcName, int denomination) : creditCardC(new CreditCard(ccNum)), businessCardC(new BusinessCard(bcName)), moneyC(new Money(denomination)) {
}
BusinessCard & businessCard() { return *businessCardC.get(); } CreditCard & creditCard() { return *creditCardC.get(); } Money & money() { return *moneyC.get(); }
Wallet & operator=(const Wallet & wtc) { Wallet w(wtc.creditCardC->cardNumber(), wtc.businessCardC->name(), wtc.moneyC->denomination()); moneyC.swap(w.moneyC); businessCardC.swap(w.businessCardC); creditCardC.swap(w.creditCardC); return *this; } };
int main() { Wallet wallet1(2201, "John's Plumbing", 5); Wallet wallet2(501, "Teds Burger's", 2);
wallet2 = wallet1; int cn = wallet2.creditCard().cardNumber(); return 0; }
Debugging - The high art and magic of cussing errors into 'features'
|
|
|
|
 |
|
 |
Ok. grin_ptr doesn't save us from writing operator=() by hand, it doesn't help with abstract classes and doesn't allow to write custom deleter. May be I'm wrong, but it can't point to nowhere. It's still useful?
|
|
|
|
 |
|
 |
~MyXa~ wrote: grin_ptr doesn't save us from writing operator=() by hand
Never said it did, so why is this an issue for you?
it doesn't help with abstract classes
Can you give an example? I do not understand.
and doesn't allow to write custom deleter.
I have never ran across a place where I needed a custom deleter, so this has never been an issue. Do you do that very often? If so, don't use grin_ptr, I guess.
May be I'm wrong, but it can't point to nowhere.
I had forgotten that I had rewritten it to get around this issue. Here is the file I use in place of arg_grin_ptr.h. You will still need the rest of Alan's arglib in order to get this to compile.
#ifndef RMW_GRIN_PTR_H #define RMW_GRIN_PTR_H
#ifndef ARG_COMPILER_H #include "arg_compiler.h"
#endif
#ifndef ARG_DEEP_COPY_UTILS_H #include "arg_deep_copy_utils.h"
#endif
#include ARG_STLHDR(algorithm)
namespace rmw {
template<typename p_type> class grin_ptr { typedef void (*delete_ftn)(p_type* p); typedef p_type* (*copy_ftn)(const p_type* p);
public:
explicit grin_ptr(p_type* pointee) : do_copy(&my_copy_ftn), p(pointee), do_delete(my_delete_ftn) { sizeof(p_type); } grin_ptr() : do_copy(&my_copy_ftn), p(NULL), do_delete(my_delete_ftn) { }
grin_ptr(const grin_ptr& rhs);
~grin_ptr() throw() { do_delete(p); }
const p_type* get() const { return p; } p_type* get() { return p; } const p_type* operator->() const { return p; } p_type* operator->() { return p; }
void reset(p_type * ptr = NULL) { do_delete(p); p = ptr; }
void release() { p = NULL; }
const p_type& operator*() const { return *p; }
p_type& operator*() { return *p; }
void swap(grin_ptr& with) throw() { p_type* pp = p; p = with.p; with.p = pp; } grin_ptr& operator=(const grin_ptr& rhs);
private: copy_ftn do_copy; p_type* p; delete_ftn do_delete;
static void my_delete_ftn(p_type* p) { delete p; } static p_type* my_copy_ftn(const p_type* p) { return arg::deep_copy(p); } };
template<typename p_type> inline grin_ptr<p_type>::grin_ptr(const grin_ptr& rhs) : do_copy(rhs.do_copy), p(do_copy(rhs.p)), do_delete(rhs.do_delete) { } template<typename p_type> inline grin_ptr<p_type>& grin_ptr<p_type>::operator=(const grin_ptr& rhs) { p_type* pp = do_copy(rhs.p); do_delete(p); p = pp; return *this;
} }
namespace std { template<class p_type> inline void swap( ::rmw::grin_ptr<p_type>& lhs, ::rmw::grin_ptr<p_type>& rhs) throw() { lhs.swap(rhs); } } #endif
~MyXa~ wrote: It's still useful?
Because of those changes, yes, it is still useful to me. Let me know if you delve into it more, and see something I missed. Also, note the comment I placed at the top of the code, pointing out a potential caveat. If you create solutions to your other problems using this code, post them, and I'll change this.
David
ps - do you have a pointer that you would recommend to use to overcome all of the limitations you pointed out?
Debugging - The high art and magic of cussing errors into 'features'
-- modified at 12:17 Thursday 27th October, 2005 (damn '<'s!)
|
|
|
|
 |
 | Great article whatever2000 | 5:25 20 Oct '05 |
|
 |
This technique seems very helpful. I think you clearly mentioned the issues where this may not apply, and there could be broad application for this. Of course we can choose to not put multiple globals in a global container, e.g. if one object is likely to be more in flux.
One thing I would suggest would be helpful is to write out all acronyms before they are first used. Anyway, thanks for a cool contribution to our collective knowledgebase.
|
|
|
|
 |
|
 |
CP = CodeProject & POD = Plain Old Data. Are those the acronyms you are talking about, or did I miss one?
I rated this intermediate, and figured that anyone who was led to read this would do so because they were intrigued by the word 'pimpl'. At that point in one's programming career they would already know about PODs, but evidently I was wrong. If I revise this again, I'll change that for you. The CP staff is so swamped with reviewing articles that I'm going to hold off revising it until something more major comes along, so this footnote will have to do for now. (And you would be surprised if you knew how many actual edits this article has seen while it was editable without CP's reviewers.)
Thanks for pointing that out, and thanks for the kind words.
David
|
|
|
|
 |
 | Not a good idea to put all globals in one place John W Wilkinson | 22:05 10 Oct '05 |
|
 |
Nice article but I do not think it is a good idea to put all your globals in one place. If you need to add a new global then everything that includes Globals.h will need to be recompiled. This could well be most of the application.
I have been bitten by this before. I now prefer to have a separate .h file for each global, e.g.:
// globalTextureManager.h
class TextureManager;
TextureManager& globalTextureManager();
// globalTextureManager.cpp
#include "globalTextureManager.h" #include "TextureManager.h"
TextureManager& globalTextureManager() { static TextureManager theTextureManager; // local static objects are guaranteed to exist when accessed, // this overcomes any creation order problems
return theTextureManager; }
|
|
|
|
 |
|
 |
John W Wilkinson wrote:
If you need to add a new global then everything that includes Globals.h will need to be recompiled.
This is true, and I tried to make an allusion to that in the comment about changing the interface to the variables held within the Globals unit. I'll modify that section to make it stand out a little more.
For my project, this has not been that big of a deal. The executable compiles to approximately 1MB, and takes about 1 minute to do a complete recompilation. If this was a 100MB executable, I'd probably be looking into this much more seriously.
As I also mentioned, I do not use only one global, either. I agree with atomizing your globals into things that make sense to you. I usually do place more than one item inside each global, though.
I would have a tendency, however, to not use static variables in order to instantiate your globals. There is nothing wrong with it, but my experience has made me gun-shy of this technique. I have been bitten by instantiation order dependency bugs more than once, and they were not fun to track down. In addition, I have faced destruction order dependency bugs more than once, and again, they were a pain to figure out what was going on when things blew up. I therefore prefer to be able to explicitly instantiate and destruct all objects however I prefer, and the static method you outlined does not easily allow you to do so. It can be done, but it is not very intuitive. The method I have outlined even makes it possible to instantiate an 'A' object, then a 'B' object, and to release them in an 'A' - 'B' order, if you ever need such action (by using 'release' in the destructors). I have never needed that, but it can be done, whereas it seems impossible using the static method.
Thank you for your comments, and helping make the article better.
David
-- modified at 5:06 Tuesday 11th October, 2005
|
|
|
|
 |
|
 |
I have proposed another solution for your consideration in the Addendum of the article. I believe it solves all the problems you brought up, but I may have missed something.
David
|
|
|
|
 |
|
 |
OK, but I still prefer the way of handling globals that I gave in which construction order problems are automatically handled, (although there is a theoretical possibility of destruction order problems).
Also, one case that your solution does not handle is an object at namespace scope that uses a global in its constructor. Such an object, being created before main is reached, will try and use a global that does not yet exist.
|
|
|
|
 |
|
 |
John W Wilkinson wrote: OK, but I still prefer the way of handling globals that I gave...
By all means, use it. I don't want to start YARW (Yet Another Religious War) over this technique. I just thought it was interesting enough to warrant an article on it, and thought a) it would expand some people's programming toolset, b) give people something interesting to think about. Who knows, maybe the 'hiding' pattern can be used for something else.
John W Wilkinson wrote: Also, one case that your solution does not handle is an object at namespace scope that uses a global in its constructor. Such an object, being created before main is reached, will try and use a global that does not yet exist.
Yes, that would be a problem. Just thinking about that design makes my skin crawl, though. I much prefer to instantiate things at namespace scope (assuming it is a class) during the ctor of the class. I usually hold a static boolean variable within the appropriate ctor, and take action if that variable is not yet changed. (ie "if (!registered) { register(); registered=true; }', where registered is the static variable initialized to false). That way all globals are guaranteed to be instantiated with the methods I use.
How often have you came across the construct you outlined in you career? Maybe it is more prevalent than I think it is.
|
|
|
|
 |
 | Nice article. raindog | 8:33 9 Oct '05 |
|
 |
Sometimes it's these simple ideas that go a really long way in making a persons programming experience that much richer.
|
|
|
|
 |
|
 |
Thank you for your kind words. I am glad you enjoyed the article.
David
|
|
|
|
 |
 | I believe I use auto_ptrs in this way all the time. John M. Drescher | 8:18 7 Oct '05 |
|
 |
I am a little confused why I can not use auto_ptrs? Are you not defining the classes that are used in the smartptrs in the implementation file?
-- modified at 13:20 Friday 7th October, 2005 Here is a header file:
#if !defined(AFX_ICADUSER_H__14EE517C_0B6D_4015_84F4_E1BB026B5234__INCLUDED_) #define AFX_ICADUSER_H__14EE517C_0B6D_4015_84F4_E1BB026B5234__INCLUDED_
#if _MSC_VER > 1000
#pragma once #endif
#include "ICADUserID.h"
#include "ICADSynchLock.h"
#include "ICADFlags.h"
class ICADUserSession; class ICADUserProps;
#include "ICADSessionID.h"
class ICADUser : public ICADFlags, public ICADSynchLock { public: ICADUser(ICADUserID & userID,DWORD dwFlags); virtual ~ICADUser(); public: enum FLAGS {ADMIN_USER=0x0001}; enum CASE_VIEW_MODES {FIRST_VIEW_MODE,RCC_LCC_RMLO_LMLO=FIRST_VIEW_MODE, RMLO_RCC_LCC_LMLO, RCC_RMLO_LCC_LMLO,NUM_VIEW_MODES}; public: void SaveProps(); HRESULT MoveToNextCaseViewMode(DWORD dwTimeOut); HRESULT GetProps(ICADUserProps *& pProps, DWORD dwMaxWait); HRESULT LoadProps(); ICADUserID GetUserID(); BOOL CreateNewSession(ICADSessionID & sessID); ICADUserSession* GetSession(); int GetCaseViewMode(DWORD dwTimeOut); BOOL IsAdminUser(); protected: ICADUserID m_userID; int m_nCaseViewMode; std::auto_ptr<ICADUserSession> m_pSession; std::auto_ptr<ICADUserProps> m_pUserProps; };
inline BOOL ICADUser::IsAdminUser() { return (GetFlags() & ADMIN_USER) != 0; }
inline ICADUserID ICADUser::GetUserID() { return m_userID; }
#endif
And the implementation:
#include "stdafx.h"
#include "ICADUser.h"
#include "ICADUserSession.h"
#include "ICADUserProps.h"
ICADUser::ICADUser(ICADUserID & userID,DWORD dwFlags) : ICADFlags(dwFlags), m_userID(userID), m_pUserProps(new ICADUserProps) { m_nCaseViewMode=RCC_LCC_RMLO_LMLO; }
ICADUser::~ICADUser() { Terminate(_T("ICADUser"),MAXIMUM_DELAY); }
int ICADUser::GetCaseViewMode(DWORD dwTimeOut) { int retVal = -1; CThreadReaderLock lock(this,FALSE); if (lock.Lock(dwTimeOut)) { retVal = m_nCaseViewMode; } return retVal; }
ICADUserSession* ICADUser::GetSession() { return m_pSession.get(); }
BOOL ICADUser::CreateNewSession(ICADSessionID & sessID) { BOOL retVal = (m_pSession.get() == NULL); if (retVal) { m_pSession = std::auto_ptr<ICADUserSession>(new ICADUserSession(sessID)); } return retVal; }
HRESULT ICADUser::GetProps(ICADUserProps *& pProps, DWORD dwMaxWait) { pProps=m_pUserProps.get(); return S_OK; }
HRESULT ICADUser::MoveToNextCaseViewMode(DWORD dwTimeOut) { HRESULT retVal; CTRLockHR lock(this,FALSE); retVal = lock.LockHR(dwTimeOut); if (SUCCEEDED(retVal)) { m_nCaseViewMode++; if (m_nCaseViewMode >= NUM_VIEW_MODES) { m_nCaseViewMode = FIRST_VIEW_MODE; } } return retVal; }
HRESULT ICADUser::LoadProps() { ICADUserProps* pProps; HRESULT retVal = GetProps(pProps,MAXIMUM_DELAY); if (pProps->LoadProps()) { int nCaseViewMode; if ( pProps->GetCaseViewPref(nCaseViewMode)) { if ( (nCaseViewMode >=0) && (nCaseViewMode m_nCaseViewMode = nCaseViewMode; } } } return retVal; }
void ICADUser::SaveProps() { ICADUserProps* pProps; if (SUCCEEDED(GetProps(pProps,MAXIMUM_DELAY))) { pProps->SetCaseViewPref(m_nCaseViewMode); } }
-- modified at 13:23 Friday 7th October, 2005
|
|
|
|
 |
|
 |
Have you checked to see that your 'm_pSession' and 'm_pUserProps' are being destroyed in your destructor? Per the standard, they will not be properly destroyed upon completion of ICADUser's destructor. Put some breakpoints in, and see for yourself. If you see a breakpoint broken upon during execution, it will probably be happening during the 'reset' operation, and NOT during the destruction of your class.
David
|
|
|
|
 |
|
 |
Thanks for the info. I'll check that out.
John
|
|
|
|
 |
|
 |
If you want to see it in action on the simplest program I found that seems to always exhibit the behaviour, see the (third post from the bottom / bottom post - two (it is so hard to describe that position correctly!) ) on the 'Re: ATOM leakage' thread on my DWinLib article.
David
-- modified at 23:23 Friday 7th October, 2005
(ps - Sometimes you do get lucky, and under a certain circumstance, when you have a certain 'simpler' header dependency order that is not shown in that thread, the correct dtor will be called, so this is an insidious bug.)
|
|
|
|
 |
|
 |
I have learned some more, and found out that as your code is structured, it will work correctly. As long as the destructor for the ICADUser is defined in the ICADUser.cpp file, the auto_ptrs will destroy themselves correctly, and you have that, so you are fine.
Sorry if I wasted any of your time over something I did not fully understand. I just knew that the construct I had used did not work, so I 'assumed', and you know what that stands for...
David
|
|
|
|
 |
|
 |
I just tested my code and the destructors are being called.
David O`Neil wrote:
As long as the destructor for the ICADUser is defined in the ICADUser.cpp file, the auto_ptrs will destroy themselves correctly, and you have that, so you are fine.
In VC6 it would not compile without the destructors defined..
[EDIT]Oh, I see now. Thanks.[/EDIT]
David O`Neil wrote:
Sorry if I wasted any of your time over something I did not fully understand.
No problem.
John
-- modified at 11:46 Monday 10th October, 2005
|
|
|
|
 |
 | but it's not pimpl. Maximilien | 6:09 7 Oct '05 |
|
|
 |
|
|
 |
|
|
Last Updated 13 Oct 2005 |
Advertise |
Privacy |
Terms of Use |
Copyright ©
CodeProject, 1999-2010