Sign in to follow this  
coordz

Pointer to member object operator

Recommended Posts

Currently I have a class like
class B;

class A {
public:
  B *bptr;
}
At certain times during my program bptr gets set to a new B. I then do things like B->myFunction(). Now I've implement the [] operator in B. All is okay upto here. Then I want to be able to do bptr[i]....... but I don't think I can because bptr is a pointer to B not actually a B itself. I think I'd have to do bptr->[i]??? Or something like that. How can I declare bptr so I can do things like bptr[i] and it call my [] operator, which is what I want to do? TIA btw bptr used to be an array which is used extensively through my code as bptr[]. I want to replace the simple array with an object with the [] operator so I don't have to re-write my code.

Share this post


Link to post
Share on other sites
Quote:
Original post by coordz
btw bptr used to be an array which is used extensively through my code as bptr[]. I want to replace the simple array with an object with the [] operator so I don't have to re-write my code.

C++ already has an object that behaves like an array, its called std::vector, it even overloads the [] operator and come with handy methods to determine the size of the array, whether its empty or not, to remove elements from anywhere in the array and all sorts really, so theres no need to write your own!
As with most things, its recommended to use the tools that come with the language.

Share this post


Link to post
Share on other sites
Quote:
Original post by dmatter
Quote:
Original post by coordz
btw bptr used to be an array which is used extensively through my code as bptr[]. I want to replace the simple array with an object with the [] operator so I don't have to re-write my code.

C++ already has an object that behaves like an array, its called std::vector, it even overloads the [] operator and come with handy methods to determine the size of the array, whether its empty or not, to remove elements from anywhere in the array and all sorts really, so theres no need to write your own!
As with most things, its recommended to use the tools that come with the language.

Thanks dmatter but sadly std::array doesn't cut it. I'm actually implementing a wrapper for a sparse array (based on std::map) which includes some additional functionality. I don't understand the STL well enough to start modifying std::map so I'm wrapping it.

Share this post


Link to post
Share on other sites
Depends on your intended usage. Your current approach is valid as long as:

  • B is a container class with a subscript operator defined (std::vector, std::map, std::deque or your own associative container).
  • An instance of class A may reference zero or one such containers, the reference must be re-seatable, the referenced container is not owned by the instance, and guaranteed to remain available for the lifetime of the instance.


As stevenmarky explained, the correct use case for this is (*this->bptr)[i].

If, however, the referenced container is internal (as you seem to imply), then you can manipulate an instance instead of a pointer, either defining it as B b; and using it as this->b[i]; instead (if you can equate the concepts of "no container" and "empty container") or defining it as boost::optional<B> b; and using it, again, as (*this->b)[i];. As a last resort, you could use auto_ptr<B> b; to emulate the external behaviour of boost::optional, though you would need to write any copy constructors for A yourself.

Of course, other ownership conditions may call for boost::shared_ptr instead.

Share this post


Link to post
Share on other sites
Quote:
Original post by coordz
I'm actually implementing a wrapper for a sparse array (based on std::map) which includes some additional functionality. I don't understand the STL well enough to start modifying std::map so I'm wrapping it.

This is fine. If you werent adding extra functionality then you might as well use a std::map directly, probably using a typedef to hide the tedious template syntax.
You won't ever want to modify the STL container directly, aggregating them in another class in this way is the correct way to extend them, along with using/writing STL compliant algorithms.
Modifying or inheriting from STL containers is a definate NO.

Share this post


Link to post
Share on other sites
Quote:
Original post by dmatter
Quote:
Original post by coordz
I'm actually implementing a wrapper for a sparse array (based on std::map) which includes some additional functionality. I don't understand the STL well enough to start modifying std::map so I'm wrapping it.

This is fine. If you werent adding extra functionality then you might as well use a std::map directly, probably using a typedef to hide the tedious template syntax.
You won't ever want to modify the STL container directly, aggregating them in another class in this way is the correct way to extend them, along with using/writing STL compliant algorithms.
Modifying or inheriting from STL containers is a definate NO.


What kind of "additional functionality", though?

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
What kind of "additional functionality", though?

Well to be honest I dont really know, off the top of my head I cant really think what you would want out of a one-dimensioned sparse array that you can't get from a vanilla std::map. Perhaps its as simple as implementing the adaptor pattern?

But in general, 'additional functionality' would just be anything that isnt provided by the std::map, whether that's in terms of the interface in the case of the adapter pattern or if you were adding some form 'higher level' functionality such as a multi-dimensional sparse array or building a resource manager perhaps.

Edit: I could possibly attempt to generalise this to "any class that provides a service whos implementation is based on an STL container should aggregate the container."

Share this post


Link to post
Share on other sites
Quote:
btw bptr used to be an array which is used extensively through my code as bptr[]. I want to replace the simple array with an object with the [] operator so I don't have to re-write my code.


When dealing with custom types and pointers, prefer to use typedefs. For example:

class B;

class A {
public:
typedef B * BPtr;

BPtr bptr;
};

...

A a;
A::BPtr p = a.bptr;

// or

class B;
typedef B * BPtr;

class A {
public:
BPtr bptr;
};

...

A a;
BPtr p = a.bptr;




But for your particular case:


class BPtr
{
public:
B &operator( size_t subscript )
{
return //get proper element from storage
}

// also override assignment operator, copy constructor, and everything else.

private:
// storage
}

class A {
public:

private:
BPtr bptr;
}


This should allow you to keep the same code elsewhere.

Share this post


Link to post
Share on other sites
Quote:
Original post by dmatter
Quote:
Original post by Zahlman
What kind of "additional functionality", though?

Well to be honest I dont really know, off the top of my head I cant really think what you would want out of a one-dimensioned sparse array that you can't get from a vanilla std::map. Perhaps its as simple as implementing the adaptor pattern?

But in general, 'additional functionality' would just be anything that isnt provided by the std::map, whether that's in terms of the interface in the case of the adapter pattern or if you were adding some form 'higher level' functionality such as a multi-dimensional sparse array or building a resource manager perhaps.

Edit: I could possibly attempt to generalise this to "any class that provides a service whos implementation is based on an STL container should aggregate the container."


Hehe. I'll wait for the OP's answer ;) But what I'm getting at is that this functionality might be better implemented at the containing-class level instead.

Share this post


Link to post
Share on other sites
Thanks guys for the responses - most helpful. The "additional functionality" is something like this..... I'm dealing with very large multi-dimensional data sets (1024x1024x4 of bytes/ints/floats) but luckily I know most of the array will be empty which means std::map is perfect. *Except* all my code so far is based around a managed (dense) array of floats (the bptr[i] type notation). std::map already provides an overloaded [] operator which could do the job.... almost. As I understand in std::map *creates* a new key/data pair if you call [] with a key which is not already in the map. What I want to do is have the [] operator act as usual if the key is in the map but if the key is not in the map I want zero (0.0f) to be returned with no extra objects created. If I use std::map as is my memory will max out super fast. By wrapping up std::map I thought I could do the check for the key and then return whatever based on if the key exists or not.

From a design point of view perhaps what I'm doing isn't the greatest but
a) there's an awful lot of code that uses the existing array directly
b) I couldn't think of anything else ;) (not a very good reason I know!)

Things are also made slightly more involved because the class actually wraps an implementation class to allow the data type stored in bptr to "dynamically" change type (see my earlier post about this).

Share this post


Link to post
Share on other sites
Quote:
Original post by coordz
What I want to do is have the [] operator act as usual if the key is in the map but if the key is not in the map I want zero (0.0f) to be returned with no extra objects created. If I use std::map as is my memory will max out super fast. By wrapping up std::map I thought I could do the check for the key and then return whatever based on if the key exists or not.


You will run into problems with this (the same problem as me, see this thread). The reason is that the user will want to use b[i] = x; to set the value at index i to x—so, you'd have to return a reference.

A solution which I could not afford, but you may find interesting, is to restrict the ways in which contents can be manipulated. Fundamentally, you would have operator[] return the following class:

template<typename key_t, typename value_t>
class reference_type
{
public:
typedef key_t key_type;
typedef value_t value_type;
typedef std::map<key_type,value_type> assoc_type;
typedef reference_type<key_type,value_type> self_type;

// Determines if map contains the element, returns
// it if it does, return the default value otherwise.
operator const value_type &() const {
assoc_type::iterator it = assoc.find(key);
if (it == assoc.end()) return default_value;
else return it->second;
}

// Inserts the new value into the map (or modifies
// it in place if it's already inside)
self_type & operator=(const value_type & v) {
assoc[key] = v;
return *this;
}

// Possibly add overloads for +=, -=, *=, /=, ++, --

// Construct
reference_type(assoc_type & assoc,
const key_type & key) :
assoc(assoc), key(key)
{}

private:
assoc_type & assoc;
key_type key;
value_type default_value;
};

template<typename key_t,value_t>
class Sparse {

typedef key_t key_type;
typedef value_t value_type;
typedef reference_type<key_type,value_type> reference_type;
typedef std::map<key_type,value_type> assoc_type;

reference_type operator[](const key_type & k) {
return reference_type(this->assoc,k);
}

private:
assoc_type assoc;
}


Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this