3

I need a way to put various items into sets. Each item can be in only 1 set at a time. I also need to be able to ask the item which set it is in. I have 3 questions:

Is this a valid implementation?

How can I use this if I split the classes into multiple header files, because each class requires the other?

Can I use a reference instead of the pointer to Manager in item class (because I don't like pointers)?

class Manager {
public:
  add_item(Item& item) {
    items.insert(&item);
    item.manager = this;
  }
  remove_item(Item& item) {
    items.erase(&item);
    item.manager = nullptr;
  }
private:
  std::unordered_set<Item*> items;
}

class Item {
public:
  Manager* get_manager() {return manager;}
private:
Manager* manager;
}
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • 3
    As for your title: Why do you need to? That sounds like you have a very serious design flaw! – πάντα ῥεῖ Sep 03 '15 at 21:35
  • I have a bunch of nodes, which are connected. The manager is a "connection" which connects various nodes. I need to be able to connect as many nodes as I want with a single connection. I also need to be able to find the name, and other info about the manager object itself, from the node (in particular, I need to find the other nodes that are connected via this connection). – Theo Sandstrom Sep 03 '15 at 21:37
  • How are you planning on retrieving the objects from the set? Once you do that, you'll know which set it's in, right? – dudeman Sep 03 '15 at 21:40
  • @TheoSandstrom What about using `std::shared_ptr`s and `std::weak_ptr`s instead of struggling with raw pointers? – πάντα ῥεῖ Sep 03 '15 at 21:42
  • Don't let the item know what container it is in. That's almost univerally a bad idea. – Puppy Sep 03 '15 at 22:50
  • @Puppy I think the question is slightly badly worded. It is more the `Item` knows what `Manager` it is _connected to_. – Chris Drew Sep 03 '15 at 22:52
  • Passing an item by reference and then storing its address in the container is a VERY BAD idea. It means you will end up with a lot of dangling pointers unless you are extremely careful elsewhere in your code, and people reading your code will wonder what on earth you are doing. If you want to store the address of an object (which I wouldn't recommend anyway) then make the function accept a pointer in the first place. – M.M Sep 03 '15 at 22:57
  • @M.M Why is it better to accept a pointer in the first place? If you accept a reference then `Manager` has an invariant that it doesn't have any null Items. If you accept a pointer then someone could add a null item. – Chris Drew Sep 03 '15 at 23:01
  • @ChrisDrew The function could check for that. If you accept a reference you may get a dangling reference. – M.M Sep 03 '15 at 23:05
  • @M.M the function could check for null, sure but then the calling code has to remember to check if an item was actually added. If you can't accept null then there is no need for any of that. You can get dangling pointers just as easily as dangling references. – Chris Drew Sep 03 '15 at 23:09
  • 1
    Yes, but normal semantics is that a function accepting references should not store a pointer to the object passed; whereas functions accepting pointers might. It's a part of self-documentation – M.M Sep 03 '15 at 23:18

4 Answers4

1

Is this a valid implementation? How can I use this if I split the classes into multiple header files, because each class requires the other?

You should use forward declarations.

In a good Object Oriented Design your object should not know how it is stored. It looks like your object should not be responsible for the activity which needs to localize the object.

std::unordered_list<Item*> items; 

This is not proper. Do You want to hash addresses of the objects?

Can I use a reference instead of the pointer to Manager in item class (because I don't like pointers)?

When passing to the function You can always replace pointers with references. There is no problem with it.

CyberGuy
  • 2,783
  • 1
  • 21
  • 31
  • Do you mean that it is incorrect to have a set of pointers? Or my mistake of saying unordered_list is incorrect? – Theo Sandstrom Sep 03 '15 at 21:40
  • @TheoSandstrom In OOP you should stick to objects not to addresses of them. It looks like you want to store them in the smart_pointer in std::set, provide equalization operator and compare each in object oriented way. – CyberGuy Sep 03 '15 at 21:43
  • I need to store them elsewhere. There will be another class with a vector of items. I need to keep track of connections between items in different objects of this new class. – Theo Sandstrom Sep 03 '15 at 21:43
  • 1
    Each node should have a container of weak_ptr's to others nodes. – CyberGuy Sep 03 '15 at 21:44
  • 1
    But I also want the connection to have its own data (e.g. a name). By your method, will I need to store that name hundreds of times, for each item that is connected? These connections will need to keep track of hundreds or thousands of items. – Theo Sandstrom Sep 03 '15 at 21:46
  • @TheoSandstrom weak_ptr does not make a copy. Your struct may look like: node { std::vector> connections; data node_data;} – CyberGuy Sep 03 '15 at 21:47
  • Wont I then have node_data stored seperately hundreds of times? – Theo Sandstrom Sep 03 '15 at 21:52
  • @TheoSandstrom once per node. – CyberGuy Sep 03 '15 at 21:54
  • @CyberGuy _"you can always replace pointers with references"_ That's not true for any standard containers! – πάντα ῥεῖ Sep 03 '15 at 21:55
  • @πάνταῥεῖ my mistake, i thought he was talking about passing it to the function. Fixing Answer. – CyberGuy Sep 03 '15 at 21:56
  • @CyberGuy I need some data for the connection as a whole (e.g name) that every node needs to access. This data needs to be stored somewhere as well. Should it just go in some object that each node carries a weak pointer to? And can this class also carry weak pointers to all nodes? – Theo Sandstrom Sep 03 '15 at 21:56
  • 1
    I suspect that noone likes the first line of your add_item implementation. You're passing in a reference to an item, then doing an address-of against it, and storing that pointer in a container... That implementation opens a world of hurt. –  Sep 03 '15 at 21:57
  • @TheoSandstrom _"I need some data for the connection ..."_ It depends on actual semantics and use cases, if you need a `std::weak_ptr` or a `std::shared_ptr`. Read more about here please: [Dynamic memory management](http://en.cppreference.com/w/cpp/memory). – πάντα ῥεῖ Sep 03 '15 at 22:00
  • That would have the same problem. You'd be passing a pointer to an item with a lifetime that is unclear. In general you need to consider that every item you put into a standard container is *copied into* it. –  Sep 03 '15 at 22:02
  • @Tive Why are you assuming the lifetime of the item is unclear? It might be perfectly clear to OP. – Chris Drew Sep 03 '15 at 22:05
  • @πάνταῥεῖ So should I have my class that actually stores the nodes keep a shared_pointer to them, and have the nodes and the manager keep a weak pointer to all other nodes? This seems like it could work. – Theo Sandstrom Sep 03 '15 at 22:06
  • @TheoSandstrom Well from the naming it sounds to be better just vice versa, supposed that the `Manager` class was meant to create the objects. Probably the naming is poorly chosen. What's your manager class supposed to _manage_ actually? Did you mean something like an `Observer` instead? In the latter case, you should use `std::weak_ptr`. – πάντα ῥεῖ Sep 03 '15 at 22:14
  • @πάνταῥεῖ I guess it is just poor naming. I did not copy my actual code, just a dummy version that shows what I want to do. So you think that this is a good way to go? – Theo Sandstrom Sep 03 '15 at 22:15
  • @TheoSandstrom Look at my very 1st comment on your question. I already let you know what I think about it. – πάντα ῥεῖ Sep 03 '15 at 22:17
  • OK. Thanks for your help. – Theo Sandstrom Sep 03 '15 at 22:20
  • @TheoSandstrom Have a look at [Design Patterns](https://sourcemaking.com/design_patterns). That was one of the sources that incented me for finding appropriate naming decades ago. Meanwhile you can take these being standard terms merely ;-). – πάντα ῥεῖ Sep 03 '15 at 22:33
  • @TheoSandstrom Also note, ask one question per question please! Everything else seriously messes up the intended FAQ/Q&A format of StackOverflow. – πάντα ῥεῖ Sep 03 '15 at 23:45
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/88821/discussion-between-theo-sandstrom-and--). – Theo Sandstrom Sep 04 '15 at 17:13
1

How can I make an object know what container it is in?

You should not need to do so ever in a proper class design. There are things like Dependency Injection, but I think that's beyond your actual needs.

Is this a valid implementation?

No

How can I use this if I split the classes into multiple header files, because each class requires the other?

Use forward declarations (have a look here for more details).

Can I use a reference instead of the pointer to Manager in item class (because I don't like pointers)?

No, you cannot.


More in depth beyond your actually asked questions, it seems to be a design issue in the end, and you're asking for XY problems.

That you don't like (raw) pointers is a very good guts feeling, what might be wrong with your actual design. Well, unfortunately you can't manage references using standard containers like std::unordered_set.

What you can do though, is using smart pointers as provided from the Dynamic memory management facilities.

Primary decision you have to take is, which of the various smart pointers like std::shared_pointer, std::weak_ptr or std::unique_ptr is the right one to manage your necessary ownership requirements semantically correct.

Also Manager might not be the best naming choice for what you want to do. Lookup the classic Design Patterns please, if there's something fitting better from these.

For example, it sounds you need something like an Observer, that tracks a number of Item changes/events, and forwards these to other registered Item instances.

Because Observer doesn't need to have ownership for any of the registered Item instances, a std::weak_ptr seems to be the right choice to reference any of them.

Community
  • 1
  • 1
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • 2
    "Can I use a reference..." / *'No, you cannot."* / *"you can't manage references using standard containers"* - Theo's not asking about `std::unordered_set`, he's asking about using `Manager& manager;` in `Item`. – Tony Delroy Sep 04 '15 at 00:43
  • @TonyD Well, that's all in all a pretty weird thread here. Mostly coming up from OPs uncleanness and asking more than one question at a time. I've been trying to answer as most generic, as the question was given. – πάντα ῥεῖ Sep 04 '15 at 00:50
  • 1
    True... bit of a rambling question, but quite a common requirement and worth trying to help with. Frustrating combination! Cheers. – Tony Delroy Sep 04 '15 at 01:42
  • @TonyD Well, I've been trying my best. Just code fixes don't help here, it seems all about concepts and design patterns IMHO. – πάντα ῥεῖ Sep 04 '15 at 01:48
  • If I am using a weak_ptr in the Observer, then won't I have to store a shared_ptr to the Item where it is actually owned? Can I use a weak_ptr to something that is just stored by value? – Theo Sandstrom Sep 04 '15 at 17:13
1

I need a way to put various items into sets. Each item can be in only 1 set at a time. I also need to be able to ask the item which set it is in. I have 3 questions:

Is this a valid implementation?

The basic ideas are sound, though it doesn't enforce your data model or defend against problematic scenarios. For an example of what you could do:

void add_item(Item& item) {
    if (item.manager == this) return; // already owner
    if (item.manager) // already has another owner...
        item.manager->remove(item);
    items.insert(&item);
    item.manager = this;
}

How can I use this if I split the classes into multiple header files, because each class requires the other?

Why would you want to? The classes seem too simple for there to be any need, and it just complicates things. (The proper way to do it is with forward declaration headers.)

Can I use a reference instead of the pointer to Manager in item class (because I don't like pointers)?

Not with your current design, because your code sets item.Manager to nullptr after removing an Item from a Manager, which is a reasonable thing to do.

More generally, you do need to ensure the actual Item objects' lifetimes span the time you're storing pointers to them in the Manager objects. That may or may not be a natural consequence of the way your code's written, or easily achieved by calling item.manager->remove(item); before the Item's destruction.

Community
  • 1
  • 1
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • The reason I want to split them into separate header files is that these are not my actual classes. I used dummy classes to demonstrate the problem that I need to solve, because my actual classes are much more complex. – Theo Sandstrom Sep 04 '15 at 20:04
  • @TheoSandstrom sounds fair. Well, as I said forward declaration headers are the way to do it - e.g. `Manager.fwd.h` contains only the line `class Manager;`, is included by `Manager.h` (to ensure they stay consistent) *and* `Item.h`. – Tony Delroy Sep 05 '15 at 07:44
0

Assuming Manager is not expected to own the Items and you have a clear idea of the lifetimes of both the Items and the Manager then this seems reasonable.

If you want to split the classes into separate header files Item.h just needs a forward declaration of Manager. In fact, as it stands, Item doesn't need a complete definition of Manager at all so you could structure it as simply as:

Item.h

class Manager;  // forward declaration

class Item {
public:
  Manager* get_manager() const { return manager; }
  void set_manager(Manager* new_manager) { manager = new_manager; }
private:
  Manager* manager = nullptr;  // Ensure initialized to null
};

Manager.h

#include "Item.h"
#include <unordered_set>

class Manager {
public:
  void add_item(Item& item) {
    if (item.get_manager())
      return;  // Item can have only one Manager
    items.insert(&item);
    item.set_manager(this);
  }
  void remove_item(Item& item) {
    items.erase(&item);
    item.set_manager(nullptr);
  }
private:
  std::unordered_set<Item*> items;
};

Live demo.

As for not liking pointers, if you want a non-owning reference that could be null then a pointer is a pretty good fit. Pointers are generally more suitable than references for member variables because references restrict what you can do with the class considerably.

Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • _"... if you want a non-owning reference..."_ I'd prefer `std::weak_ptr` for semantically clarity. – πάντα ῥεῖ Sep 03 '15 at 23:16
  • 1
    @πάντα ῥεῖ In my opinion a `std::weak_ptr` is for a non-owning reference when you don't have a clear idea of lifetimes. If you do have a clear idea of lifetimes then a raw-pointer is fine. A `std::weak_ptr` is a non-owning reference with an option of ownership in the future and so can cause confusion. – Chris Drew Sep 03 '15 at 23:18
  • _"If you do have a clear idea of lifetimes then a raw-pointer is fine. "_ Fair point. Anyway `std::weak_ptr` stays on _the safe point_ site ;-). – πάντα ῥεῖ Sep 03 '15 at 23:23