Jump to content
  • Advertisement
Sign in to follow this  
grill8

Questions about a couple of strange scenarios in C++.

This topic is 4140 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello all, I have a couple of questions about strange scenarios that can arise in C++ which I do not know how to handle properly or in a non-sloppy way. 1) The first scenario is this: I have an std::map with a traits class as the key and a resource class as the element, as in: std::map<CTraits, CResource> m_mResources; The element CResource requires an iterator to the matching CTraits class as a member. I would like to construct the CResource class with an iterator in its constructor but as far as I know there is not an iterator that exists until AFTER the CResource has been constructed and inserted. Therefore the only possible solution that I can think of is initializing the CResource class iterator AFTER it has been inserted to the map. Something like: iterator it = m_mResources.insert(...); it->second.SetBackReferenceIterator(it); This is sloppy in many ways because A) the CResource class will require a SetBackReferenceIterator(...) method that should never be called elsewhere which leads to a private:/friend relationship to the maintaining class (very sloppy for such a simple problem), and B) during construction/insertion of the CResource a member (the iterator) will not properly be constructed (i.e, similar to a dangling pointer) which should never happen...so...in short this solution is a hack. As far as I know though the iterator of a key/value pair cannot be known until AFTER insertion...so...I do not know how to avoid this hack. In short I need to know if there even exists a way to know the inserted iterator of a key/value pair DURING construction of the inserting key/value pair. 2) The second scenario I have to deal with is more of a syntactical C++ question. Ok, so when you make a class a friend of another class, it can then access your private/protected methods/members. My problem with this relationship is that the friend can directly have access to members. Methods are fine but having access to members as well seems to overpower the befriended class. So, my question is, is there a way to make a class a friend but only grant it access to protected/private methods (but NOT members) as well? Thank you for your help. Jeremy

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by grill8
2) The second scenario I have to deal with is more of a syntactical C++ question. Ok, so when you make a class a friend of another class, it can then access your private/protected methods/members. My problem with this relationship is that the friend can directly have access to members. Methods are fine but having access to members as well seems to overpower the befriended class.
So, my question is, is there a way to make a class a friend but only grant it access to protected/private methods (but NOT members) as well?

You can create a superclass with private members and use public inheritance to a subclass and make any friends to that subclass. Those friends will never see private members from the superclass, but neither will the subclass. So, if you make the subclass a superclass' friend, it can have access to its private members and friends of the subclass will still have not. You can check it by doing:


class Super
{
friend class Sub;

public:
static int publ;

protected:
static int prot;

private:
static int priv;
};


class Sub : public Super
{
friend struct X;

public:
sub()
{
priv = 0; // has free access, since its Super's friend
prot = 0;
publ = 0;
}

static int subpubl;

private:
static int subpriv;
};


struct X
{
X()
{
sub::subpubl = 0;
sub::subpriv = 0;
sub::priv = 0; // error -- can't access
sub::prot = 0;
sub::publ = 0;
}
};



But this is messy and is more like a flaw in any design.

Share this post


Link to post
Share on other sites
Jaiminho -
Thank you. Yeah that particular structure in an of itself is a bit messy but I get what you are talking about as far as the design structure is concerned (abstracting the members to a separate derivable class which is then a friend). Thank you very much for your help.

Share this post


Link to post
Share on other sites
Regarding the first question, I'm not sure that there's any way to initialize the iterator in the constructor as you describe.

However, I have to ask why you need to store this iterator in the first place. In general, it's a bad idea (IMO) to let objects manipulate the container in which they reside (or for that matter, to even allow them to know that they're in a container).

If the iterator isn't going to be used to access or modify the contents of the container, then the only reason I can think of for its existence is to give the resource access (via the 'first' member variable of the iterator) to its corresponding traits object.

If this is really necessary (that is, if the resource really needs to know about the traits object with which it's paired), there's probably a better way to handle it than via a map iterator. What those ways might be probably depends on a few things, such as what the traits class does exactly, why the resource needs to know about it, and so on.

I'll also mention that I've run into a similar problem before when storing 'named' objects in a map. For example, the settings in my console system are stored in a map by name, but they also 'know' their own name, mostly for convenience (displaying their state, streaming error messages to the console, and so on). This means that each name is stored twice: once in the map (as the key), and once in the 'value' object itself.

I haven't yet figured out a way around this redundancy, but I'm sure one of the gurus here could easily point out one or more obvious solutions. I mention this simply because such solutions might apply to your question as well (the question being, in abstract terms, what to do when the values in a map need to 'know' their corresponding key?).

Share this post


Link to post
Share on other sites
Jyk -
Thank you. The reason I am storing the iterator in the resource class is because it (as you mentioned) provides access to the traits that are associated with the instance. It also is stored because it provides quicker deletion for the resource by deleting by iterator rather than requiring a map search for the traits class on removal of the CResource.

By storing the iterator you can access the traits if needed using something like:

const CTraits& GetTraits(){return m_iterator->first;)

also, to delete the resource you can simply delete it by it's iterator which is a simple operation in comparison to having to search the map for a matching CTraits class every time a resource is deleted.

All-
This topic remains open.


Thank you, Jeremy (grill8)



Share this post


Link to post
Share on other sites
Quote:
Original post by grill8
Jyk -
Thank you. The reason I am storing the iterator in the resource class is because it (as you mentioned) provides access to the traits that are associated with the instance. It also is stored because it provides quicker deletion for the resource by deleting by iterator rather than requiring a map search for the traits class on removal of the CResource.

By storing the iterator you can access the traits if needed using something like:

const CTraits& GetTraits(){return m_iterator->first;)

also, to delete the resource you can simply delete it by it's iterator which is a simple operation in comparison to having to search the map for a matching CTraits class every time a resource is deleted.
Just a couple more quick comments before heading off to bed...

I would again suggest that the resource class not be allowed to modify the container in which it resides, nor to have non-mutable access via a constant iterator.

There are various reasons for this, but for brevity I'll just name one. Your current scheme more or less ties the resource class to a particular container type. Sure, a map is probably the most logical container where resources are concerned, but what if (for whatever reason) you wanted to store some resources in a vector or list? What if you ran into a situation where you needed a 'free' resource not associated with any container at all? What if (as is likely to happen eventually) you decide to store resources by reference (most likely using a smart pointer) rather than by value? In all of these cases (and probably others I haven't thought of) the 'map iterator' member field of the resource class becomes at best an over-specification, and at worst a liability (e.g. the necessarily invalid iterator stored by a 'free' resource or an 'orphaned' reference-counted resource).

Also, efficiency probably isn't the best argument in favor of storing the iterator. Even if resource management were to become a bottleneck (which seems unlikely), there are probably other ways you could optimize things that would be better in terms of design.

Anyway, once you've dispensed with the container manipulation aspect of the problem, you're left with the simple problem of referencing the associated key in some way. As my 'console setting map' example above illustrates, I don't have a ready solution to this problem (I will say though that it might help others to propose possible solutions if you were to post the traits class, and perhaps describe why exactly the resource and its associated traits need to be linked).

Share this post


Link to post
Share on other sites
Quote:
Original post by grill8
1) The first scenario is this: I have an std::map with a traits class as the key and a resource class as the element, as in:

std::map<CTraits, CResource> m_mResources;

The element CResource requires an iterator to the matching CTraits class as a member.
After only reading that far I can tell you that you're doing something very wrong. That is never the right way to do something. It's like writing in your diary where your diary is currently being kept, which makes no sense at all.

Obviously you've thought too hard about something with not enough caffiene or sleep, and wandered into a line of thinking where you can't see the obvious and proper way to do it.
Try describing the kinds of things you do with m_mResources, to us (maybe give more code snippets), and someone here will probably show you the right path to take.

Share this post


Link to post
Share on other sites
I'm actually about to revise my ResourceCache to something similar. Currently its using a string as the key value, and a smart-pointer to my resource object (the Cache itself retains a reference to the resource. The resource object knows its key, which, for now, is simply the filename, but it is redundant.

I've created a class I call ResourceInfo, which holds either the filename or an HINSTANCE + ID for embedded resources. The purpose of the ResourceInfo class is to separate data that IDs the resource from the resource itself. The ResourceInfo data is used to identify whether or not the data is currently loaded and to restore resources in the event that the display device is lost.

The container would never be modified through any of the resource objects, only through the inner workings of the ResourceCache itself, for example, when the ref-count of a resource reaches 1 (meaning that ResourceCache itself is the only reference) it may be collected depending on the selected CollectionPolicy and/or available memory.

Share this post


Link to post
Share on other sites
So the reason you want to store an iterator inside a CResource is to allow quicker removals from the containing map?

Well if that's the case, why not hold on to an std::map<CTraits, CResource>::iterator where you were previously holding just a CResource?

This allows you access to the CResource and of course its position in the container!

If that still feels dirty, you could provide a wrapper around an std::map<CTraits, CResource>::iterator. A sketch:


class CResourceWrapper
{
public:
// ...

// for quick and easy access to CResource members
CResource *operator->() const
{
return &iter_->second;
}

const std::map<CTraits, CResource>::iterator &iterator() const
{
return iter_;
}

private:
std::map<CTraits, CResource>::iterator iter_;
};



You'd probably have to make a version for const CResources, too, I guess.

Or you could use something like boost::transform_iterator to do this for you.

Edd

Share this post


Link to post
Share on other sites
Jyk -

Yeah, your concerns are valid. I agree that unless the situation was truly a bottleneck than storing an iterator elsewhere would probably not be such a good idea. Even if there were 1024 resources in the map then that would only be 10 key comparisons at most, not enough to warrant storing the iterator for optimization.

After careful consideration I have decided to...instead....

1) Make a couple of template class/s something along the lines of:
(wrote this quick in pseudo-code)

template<TResource, TTraits>
class TCResourceContainer
{
public:
ctor(const TTraits& crTraits)
: m_tclTraits(crTraits),
m_tclResource(m_tclTraits){}

const TTraits& GetTraits() {return m_tclTraits;}
const TResource& GetResource(){return m_tclResource;}

private:
TTraits m_tclTraits;
TResource m_tclResource;
};

template<TResource, TTraits>
class TCResource
{
private:
ctor(const TCResourceContainer<TResource, TTraits>& crCont)
: m_ptclResCont(crCont){}

public:
const TTraits& GetTraits() {return m_ptclResCont->GetTraits();}
const TResource& operator()(){return m_ptclResCont->GetResource();}

private:
const TCResourceContainer<TResource, TTraits>& m_ptclResCont;
};

2) Use an std::set rather than an std::map to store TCResourceContainer(s)

3) The TCResourceContainer will be stored by the manager and the TCResource(s) will be instanced/deleted by the manager. By maintaining a reference to the container rather than the traits allows for simple deletion.

Obviously there will be a resource manager that keeps track of instancing/ref counting/caching capabilities etc. But in short the containers can be stored in a set (better than a map memory wise) with no dangling iterators/pointers/references etc.

So...in short I believe it is solved actually (worked it out in my head but I still need to write up some documentation). Thank you for your help Jyk.

All -
This topic is closed.

Jeremy (grill8)


Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!