简体   繁体   中英

C++ Design: Pool and pointers VS client-server

I'm designing a software tool in which there's an in-memory model, and the API user can get objects of the model, query them and set values.

Since all the model's objects belong to a single model, and most operations must be recorded and tested, etc., each created object must be registered to the Model object. The Model stores all objects as std::unique_ptr since it's the only owner of them. When needed, it passes raw pointers to users.

What makes me worry is the possibility that the user calls delete on these pointers. But if I use std::shared_ptr , the user can still use get() and call delete on that. So it's not much safer.

Another option I though of is to refer to objects by a name string, or pass ObjectReference objects instead of the real objects, and then these ObjectReferences can be destroyed without affecting the actual stored object.

These References work somewhat like a client: You tell them what to do, and they forward the request to the actual object. It's a lot of extra work for the developer, but it protectes the pointers.

Should I be worried about the pointers? Until now I was using smart pointers all the time, but now I need to somehow allow the user to access objects managed by a central model, without allowing the user to delete them.

[Hmmm... maybe make the destructor private, and let only the unique_ptr have access to it through a Deleter?]

You shouldn't bother about users calling delete on your objects. It's one of those things that are perfectly fine as a documented constraint, any programmer violating that only deserves whatever problem he runs into.

If you still really want to explicitly forbid this, you could either write a lightweight facade object that your users will pass by value (but it can be lot of work depending on the number of classes you have to wrap) or, as you said, make their destructor private and have unique_ptr use a friend deleter.

I for one am not fond of working through identifiers only, this can quickly lead to performance issues because of the lookup times (even if you're using a map underneath).


Edit: Now that I think of it, there is a way in between identifiers and raw pointers/references: opaque references .

From the point of view of the users, it acts like an identifier, all they can do is copy/move/assign it or pass it to your model.

Internally, it's just a class with a private pointer to your objects. Your model being a friend of this class, it can create new instances of the opaque reference from a raw pointer (which a user can't do), and use the raw pointer to access the object without any performance loss.

Something along the lines of:

class OpaqueRef
{
    // default copy/move/assignment/destructor
private:
    friend class Model;
    Object* m_obj;
    OpaqueRef(Object& obj) : m_obj(&obj) {}
};

Still, not sure if it's worth the trouble (I stand by my first paragraph), but at least you got one more option.

Personally, I'd keep the internal pointer in the model without exposing it and provide an interface via model ids, so all operations go through the interface.

So, you could create a separate interface class that allows modification of model attributes via id. External objects would only request and store the id of the object they want to change.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM