473,418 Members | 1,980 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,418 software developers and data experts.

can std::set hold pointers to keys instead of the keys themselves?

I never had any problems storing pointers in STL
containers such std::vector and std::map. The benefit
of storing pointers instead of the objects themselves
is mainly saving memory resources and performance (STL
containers hold *copies* of what's passed to them via
the insert() method).

However, I am not sure how to accomplish that using
std::set. For various reasons, I cannot use vector or
map in my application. But I would like to use the
same optimization I have always used with STL
containers, which is letting them store pointers to
the items, not the items themselves.

In particular, the following code snippet compiles and
works beautifully (albeit no so efficient):

================ WORKING CODE SNIPPET ================
typedef std::set<CItemKey, std::less<CItemKey> > TItemKeys;
typedef TItemKeys::const_iterator TItemKeysConstIterator;
typedef TItemKeys::iterator TItemKeysIterator;

TItemKeys m_items;

const CItem* CContainer::getItem(const CItemKey& rItemKey) const
{
TItemKeysConstIterator it = m_items.find(rItemKey);

if (it != m_items.end())
return static_cast<const CItem*>(it->getOwner());
else
return (NULL);
}
================================================== ====
However, when I modify this to be pointer based
(notice the subtle difference), it does not even
compile! It fails on the find() statement for
inability to convert the type of the parameter (or the
return value):

================ WORKING CODE SNIPPET ================
typedef std::set<CItemKey*, std::less<CItemKey> > TItemKeys;
typedef TItemKeys::const_iterator TItemKeysConstIterator;
typedef TItemKeys::iterator TItemKeysIterator;

TItemKeys m_items;

const CItem* CContainer::getItem(const CItemKey& rItemKey) const
{
TItemKeysConstIterator it = m_items.find(rItemKey);

if (it != m_items.end())
return static_cast<const CItem*>(it->getOwner());
else
return (NULL);
}
================================================== ====

What am I doing wrong?

I also tried modifying m_items.find(rItemKey) to
m_items.find(&rItemKey) but that doesn't help either.
Obviously I ran into a mental block here.

Is there a way to accomplish what I am trying to
achieve (storing pointers in a set)? Or is this
fundamentally incorrect, as set by definition must
contain keys, not pointers to keys?

Thanks.

Dec 17 '05 #1
10 7514
da****@my-deja.com wrote:
[..]
However, when I modify this to be pointer based
(notice the subtle difference), it does not even
compile! It fails on the find() statement for
inability to convert the type of the parameter (or the
return value):

================ WORKING CODE SNIPPET ================
Did you mean "NON-WORKING CODE SNIPPET"?

typedef std::set<CItemKey*, std::less<CItemKey> > TItemKeys;
typedef TItemKeys::const_iterator TItemKeysConstIterator;
typedef TItemKeys::iterator TItemKeysIterator;

TItemKeys m_items;

const CItem* CContainer::getItem(const CItemKey& rItemKey) const
{
TItemKeysConstIterator it = m_items.find(rItemKey);
The trick is that you're looking for the element in 'm_items' that
_points_ to the same _value_ as 'rItemKey', but 'find' only looks
for the same _pointer_. Changing it to

TItemKeysConstIterator it = m_items.find(&rItemKey);

should help it compile, but I don't see it work very well because
the item you pass will very unlikely have the same address as any
item in the set.

You probably want to use 'std::find' with your own, custom predicate
that will dereference the pointer and compare it to the value you
need.
if (it != m_items.end())
return static_cast<const CItem*>(it->getOwner());
else
return (NULL);
}
================================================== ====

What am I doing wrong?

I also tried modifying m_items.find(rItemKey) to
m_items.find(&rItemKey) but that doesn't help either.
Doesn't help in what way? Does it compile?
Obviously I ran into a mental block here.

Is there a way to accomplish what I am trying to
achieve (storing pointers in a set)? Or is this
fundamentally incorrect, as set by definition must
contain keys, not pointers to keys?


Well, you're twisting the definition. Your set doesn't contain
_pointers_to_keys_. It contains _pointers_. They are both values
and keys. Now, if you had some kind of custom comparator to make
your stored values compared with dereferencing, then you might get
closer to achieving what you want. Look at the second template
argument for std::set.

V
Dec 17 '05 #2
da****@my-deja.com wrote:
I never had any problems storing pointers in STL
containers such std::vector and std::map. The benefit
of storing pointers instead of the objects themselves
is mainly saving memory resources and performance (STL
containers hold *copies* of what's passed to them via
the insert() method).

However, I am not sure how to accomplish that using
std::set. For various reasons, I cannot use vector or
map in my application. But I would like to use the
same optimization I have always used with STL
containers, which is letting them store pointers to
the items, not the items themselves.

In particular, the following code snippet compiles and
works beautifully (albeit no so efficient):

================ WORKING CODE SNIPPET ================
typedef std::set<CItemKey, std::less<CItemKey> > TItemKeys;
typedef TItemKeys::const_iterator TItemKeysConstIterator;
typedef TItemKeys::iterator TItemKeysIterator;

TItemKeys m_items;

const CItem* CContainer::getItem(const CItemKey& rItemKey) const
{
TItemKeysConstIterator it = m_items.find(rItemKey);

if (it != m_items.end())
return static_cast<const CItem*>(it->getOwner());
else
return (NULL);
}
================================================== ====
However, when I modify this to be pointer based
(notice the subtle difference), it does not even
compile! It fails on the find() statement for
inability to convert the type of the parameter (or the
return value):

================ WORKING CODE SNIPPET ================
typedef std::set<CItemKey*, std::less<CItemKey> > TItemKeys;
You are using a comparison function that produces a type mismatch. Either
use std::less<T*> or something like

pointee_comapare ( CItemKey const * a, CItemKey const b * b ) {
return (*a) < (*b);
}
typedef TItemKeys::const_iterator TItemKeysConstIterator;
typedef TItemKeys::iterator TItemKeysIterator;

TItemKeys m_items;

const CItem* CContainer::getItem(const CItemKey& rItemKey) const
{
TItemKeysConstIterator it = m_items.find(rItemKey);

if (it != m_items.end())
return static_cast<const CItem*>(it->getOwner());
else
return (NULL);
}
================================================== ====

What am I doing wrong?

I also tried modifying m_items.find(rItemKey) to
m_items.find(&rItemKey) but that doesn't help either.
Obviously I ran into a mental block here.

Is there a way to accomplish what I am trying to
achieve (storing pointers in a set)? Or is this
fundamentally incorrect, as set by definition must
contain keys, not pointers to keys?


A map can contain pointers. However, recall that a std::map<T> uses
std::less<T> to keep the entries sorted for fast retrieval. If you use
std::map<T*> instead, the comparison predicate used by default will compare
the pointers not the pointees. If you want to use the pointers just for
efficiency, this will not be what you want. Instead, you need to provide
your own comparison function instead of std::less<T*> that forwards the
comparison to the pointees.
Best

Kai-Uwe Bux

Dec 17 '05 #3
Victor Bazarov wrote:
Did you mean "NON-WORKING CODE SNIPPET"? Yes. Sorry for the typo. The second code snippet is the non-working
one.

The trick is that you're looking for the element in 'm_items' that
_points_ to the same _value_ as 'rItemKey', but 'find' only looks
for the same _pointer_. Changing it to

TItemKeysConstIterator it = m_items.find(&rItemKey);

should help it compile, but I don't see it work very well because
the item you pass will very unlikely have the same address as any
item in the set.


You are right and I knew that even before the change. However, since I
am not that proficient in STL, I wanted first to make it compile and
then take care of correct comaprison function.

Right now I cannot decipher the cryptic compilaation error messages
that VC++ gives me.

It says:

============ START QUOTE ================
'class std::_Tree<class CItemKey *,
class CItemKey *,
struct std::set<class CItemKey *,
struct std::less<class CItemKey *>,
class std::allocator<class CItemKey *> >::_Kfn,
struct std::less<class CItemKey *>,
class std::allocator<class CItemKey *> >::const_iterator __thiscall
std::set<class CItemKey *,
struct std::less<class CItemKey *>,
class std::allocator<class CItemKey *> >::find(class CItemKey *const &
) const' :
cannot convert parameter 1 from 'const class CItemKey *' to 'class
CItemKey *const & '

Reason: cannot convert from 'const class CItemKey *' to 'class
CItemKey *const '
Conversion loses qualifiers
============ END QUOTE ================

Obviously there is a constness problem here, but I don't understand
where a conversion from 'const CItemKey *' to 'CItemKey *const' was
attempted.

Dec 18 '05 #4
da****@my-deja.com wrote:
Obviously there is a constness problem here, but I don't understand
where a conversion from 'const CItemKey *' to 'CItemKey *const' was
attempted.


OK - I found the problem:

All I had to do to make the set::find() call compile was to cast the
parameter "properly". The resulting compilable code looks like this:

================= START QUOTE =============
const CItem* CContainer::getItem(const CItemKey& rItemKey) const
{
CItemKey* const pItemKeyToFind =
const_cast<CItemKey* const>(&rItemKey);

TItemKeysConstIterator it = m_items.find(pItemKeyToFind);

(*it)->getContainer();

if (it != m_items.end())
return static_cast<const CItem*>((*it)->getOwner());
else
return (NULL);
}
================= END QUOTE =============

I now have to take care of handling the comparison correctly (so that
it it doesn't compare pointers but pointees).

My only confusion now is why is the cast needed? That is, why was
set::find() implemented in such a way that it would require a
<CItemKey* const> as a parameter, while <const CItemKey* const> simply
would not compile?

This is especially baffling in light of the fact that set::find is
declared as follows:

const_iterator find(const Key& key) const;

which means that the both the item and what points to it are const. Or
am I missing something here?

Dec 18 '05 #5
> My only confusion now is why is the cast needed? That is, why was
set::find() implemented in such a way that it would require a
<CItemKey* const> as a parameter, while <const CItemKey* const> simply
would not compile?

This is especially baffling in light of the fact that set::find is
declared as follows:

const_iterator find(const Key& key) const;

which means that the both the item and what points to it are const. Or
am I missing something here?
The problem with the `const` qualifier is that it applies to what is
written to the _left_. For example:

int * const i; // A constant pointer to non-constant int
int const * j; // A non-constant pointer to constant int
int const * const k; // Everything is constant

The only exception to this is when `const` is the leftmost word. In
effect `const int *` is the same as `int const *`.

Now when you take a look at the type of `find` method's first parameter,
you see it is `const Key &`. By applying the above rule, this is
equivalent to `Key const &`. When the template gets specialized, it
changes to `CItemKey * const &`. That's why you cannot pass `const
CItemKey *`, the types are not compatible.

I believe that instead of casting away constness, you should change the
template parameter to `const CItemKey *` like this:

typedef std::set<const CItemKey *> ItemKeys;
I now have to take care of handling the comparison correctly (so that
it it doesn't compare pointers but pointees).


You can do that by writing your own comparison function and use it
instead of `std::less`. For example:

bool MyLess(const CItemKey * x, const CItemKey * y)
{
return (*x) < (*y);
}

typedef std::set<const CItemKey *, MyLess> ItemKeys;

I hope this was helpful.
Martin.
Dec 18 '05 #6

da****@my-deja.com wrote:
I never had any problems storing pointers in STL
containers such std::vector and std::map. The benefit
of storing pointers instead of the objects themselves
is mainly saving memory resources and performance (STL
containers hold *copies* of what's passed to them via
the insert() method).

However, I am not sure how to accomplish that using
std::set. For various reasons, I cannot use vector or
map in my application. But I would like to use the
same optimization I have always used with STL
containers, which is letting them store pointers to
the items, not the items themselves.

In particular, the following code snippet compiles and
works beautifully (albeit no so efficient):

================ WORKING CODE SNIPPET ================
typedef std::set<CItemKey, std::less<CItemKey> > TItemKeys;
typedef TItemKeys::const_iterator TItemKeysConstIterator;
typedef TItemKeys::iterator TItemKeysIterator;

TItemKeys m_items;

const CItem* CContainer::getItem(const CItemKey& rItemKey) const
{
TItemKeysConstIterator it = m_items.find(rItemKey);

if (it != m_items.end())
return static_cast<const CItem*>(it->getOwner());
else
return (NULL);
}
================================================== ====
However, when I modify this to be pointer based
(notice the subtle difference), it does not even
compile! It fails on the find() statement for
inability to convert the type of the parameter (or the
return value):

================ WORKING CODE SNIPPET ================
typedef std::set<CItemKey*, std::less<CItemKey> > TItemKeys;
typedef TItemKeys::const_iterator TItemKeysConstIterator;
typedef TItemKeys::iterator TItemKeysIterator;

TItemKeys m_items;

const CItem* CContainer::getItem(const CItemKey& rItemKey) const
{
TItemKeysConstIterator it = m_items.find(rItemKey);

if (it != m_items.end())
return static_cast<const CItem*>(it->getOwner());
else
return (NULL);
}
================================================== ====

What am I doing wrong?

I also tried modifying m_items.find(rItemKey) to
m_items.find(&rItemKey) but that doesn't help either.
Obviously I ran into a mental block here.

Is there a way to accomplish what I am trying to
achieve (storing pointers in a set)? Or is this
fundamentally incorrect, as set by definition must
contain keys, not pointers to keys?

Thanks.


Consider using a smart pointer that uses value semantic.
Example:
http://code.axter.com/copy_ptr.h

The above smart pointer uses value semantic instead of poitner
semantic, so when a comparison is performed, it compares the object
instead of the address of the pointer.
The copy_ptr works great with std::set.
You can also use the following COW pointer which also uses value
semantics:
http://code.axter.com/cow_ptr.h

Dec 18 '05 #7
Martin Vejnar wrote:
I hope this was helpful.
Yes, thank you - it was very helpful.
I believe that instead of casting away constness, you should change the
template parameter to `const CItemKey *` like this:

typedef std::set<const CItemKey *> ItemKeys;

But that means that the items stored (not the pointers to them) are
const, right? If so, this is not what I need. I need a container
(std::set in this case) that stores modifiable items.
You can do that by writing your own comparison function and use it
instead of `std::less`. For example:

bool MyLess(const CItemKey * x, const CItemKey * y)
{
return (*x) < (*y);
}

typedef std::set<const CItemKey *, MyLess> ItemKeys;


I used a function object for that purpose (works beautifuly). Is there
a fundamental difference between your approach and the function object
approach?

Thanks.

Dec 20 '05 #8
da****@my-deja.com wrote:
Martin Vejnar wrote:
I believe that instead of casting away constness, you should change the
template parameter to `const CItemKey *` like this:

typedef std::set<const CItemKey *> ItemKeys;
But that means that the items stored (not the pointers to them) are
const, right? If so, this is not what I need. I need a container
(std::set in this case) that stores modifiable items.


You shouldn't modify keys of sortable containers. If you do so, the
`std::set` will no longer be sorted correctly. If you need to change a
value in `std::set` you must `erase` and re-`insert` it. But if you just
want to store key-value pairs you're better off using `std::map`.
You can do that by writing your own comparison function and use it
instead of `std::less`. For example:

bool MyLess(const CItemKey * x, const CItemKey * y)
{
return (*x) < (*y);
}

typedef std::set<const CItemKey *, MyLess> ItemKeys;


I used a function object for that purpose (works beautifuly). Is there
a fundamental difference between your approach and the function object
approach?


Fundamental difference? I have no idea.

Martin.
Dec 20 '05 #9
Martin Vejnar wrote:
You shouldn't modify keys of sortable containers. If you do so, the
`std::set` will no longer be sorted correctly. If you need to change a
value in `std::set` you must `erase` and re-`insert` it.


You are right. I overlooked this point. Thanks for clarifying this
issue for me.
(fortunately I haven't reached a case in which I need to modify those
key objects while in the container, but if I do, I will make sure that
I do it the way you outlined).
You can do that by writing your own comparison function and use it
instead of `std::less`. For example:

bool MyLess(const CItemKey * x, const CItemKey * y)
{
return (*x) < (*y);
}

typedef std::set<const CItemKey *, MyLess> ItemKeys;


I used a function object for that purpose (works beautifuly). Is there
a fundamental difference between your approach and the function object
approach?


Fundamental difference? I have no idea.


This is what I did:
class CItemKeyPtrLess
{
public:
bool operator()(const CItemKey* const pKeyA,
const CItemKey* const pKeyB) const
{
return (*pKeyA) < (*pKeyB);
}
};

(I know I could have used a struct there, but I preferred sticking to
the textbook (by Leen Ammeraal).

I am curious to know whether there is an advantage of using either way.

Dec 20 '05 #10
In article <11**********************@g49g2000cwa.googlegroups .com>,
da****@my-deja.com wrote:
Martin Vejnar wrote:
You shouldn't modify keys of sortable containers. If you do so, the
`std::set` will no longer be sorted correctly. If you need to change a
value in `std::set` you must `erase` and re-`insert` it.


You are right. I overlooked this point. Thanks for clarifying this
issue for me.
(fortunately I haven't reached a case in which I need to modify those
key objects while in the container, but if I do, I will make sure that
I do it the way you outlined).
>You can do that by writing your own comparison function and use it
>instead of `std::less`. For example:
>
> bool MyLess(const CItemKey * x, const CItemKey * y)
> {
> return (*x) < (*y);
> }
>
> typedef std::set<const CItemKey *, MyLess> ItemKeys;
>

I used a function object for that purpose (works beautifuly). Is there
a fundamental difference between your approach and the function object
approach?


Fundamental difference? I have no idea.


This is what I did:
class CItemKeyPtrLess
{
public:
bool operator()(const CItemKey* const pKeyA,
const CItemKey* const pKeyB) const
{
return (*pKeyA) < (*pKeyB);
}
};

(I know I could have used a struct there, but I preferred sticking to
the textbook (by Leen Ammeraal).

I am curious to know whether there is an advantage of using either way.


Your way may be slightly faster than using the pointer to function
because yours is more likely to be inlined. The speed difference is
probably nothing you would ever notice though.
--
Magic depends on tradition and belief. It does not welcome observation,
nor does it profit by experiment. On the other hand, science is based
on experience; it is open to correction by observation and experiment.
Feb 3 '06 #11

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

26
by: Michael Klatt | last post by:
I am trying to write an iterator for a std::set that allows the iterator target to be modified. Here is some relvant code: template <class Set> // Set is an instance of std::set<> class...
8
by: gipsy boy | last post by:
I've got a std::set with a custom comparator. When I add things, it puts them in the right place. When I change these objects (and I can tell they are effectively changed), the set doesn't...
5
by: Peter Jansson | last post by:
Hello, I have the following code: std::map<int,std::set<std::string> > k; k="1234567890"; k="2345678901"; //... std::set<std::string> myMethod(std::map<int,std::set<std::string> > k)...
5
by: asdf | last post by:
I have a program that reads sorted data from a database and inserts it element by element into a set. If the data is sorted is there a faster way to insert ? Meaning is there a way to tell the...
15
by: desktop | last post by:
If I have a sorted std::list with 1.000.000 elements it takes 1.000.000 operations to find element with value = 1.000.000 (need to iterator through the whole list). In comparison, if I have a...
6
by: brzrkr0 | last post by:
A portion of my program needs to initialize a std::set<intto have all the keys in a std::map<int, double>. The code I've pasted below works, but I'm wondering if there's a more elegant way to do...
2
by: mathieu | last post by:
hi there, I would like to know if the following piece of code is garantee to work. I am afraid that the cstring address I am using in the std::map found from a request in std::set is not...
7
by: Christian Meier | last post by:
Hello Newsgroup I have a question about the find function of std::set. When I have a "std::set<int*>", why can't I call the find() function with an "const int*"? I know that my key type is...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...
0
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing,...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.