What is the right way to expose resources owned by a class?

孤人 提交于 2019-12-04 00:02:07

As others have answered from a technical standpoint, I'd like to point you at a different approach and revise your design. The idea is to try to respect the Law of Demeter and don't grant access to object's sub-components. It's a bit harder to do, and without a specific example I can't provide many details but try to imagine a class Book formed of Pages. If I want to print one, two or more pages of the book, with your current design I can do:

auto range = ...;
for( auto p : book.pages(range) )
  {
  p->print();
  }

while abiding by Demeter's you'll have

auto range = ...;
book.print( /* possibly a range here */ );

this is slanted towards better encapsulation as you don't rely upon internal details of the book class and if its internal structure changes, you don't need to do anything to your client code.

I'd return Bar& (perhaps with a const).

The user will need to understand that the reference will be invalidated if the element is removed from the map - but this will be the case whatever you do, due to the single-ownership model.

I usually return references to the data not the reference to the unique_ptr:

const Bar &getBar(std::string name) const {
    return *bars[name];
}

If you want to be able to return empty item you can return raw pointer (and nullptr in case of empty). Or even better you can use boost::optional (std::optional in C++14).

If there is possibility that the reference would survive longer than the owner (multithreaded environment) I use shared_ptr internally and return weak_ptr in access methods.

I'd return a std::weak_ptr< Bar > (if C++11) or boost::weak_ptr (if not C++11). This makes it explicit that this is heap memory and doesn't risk dangling a reference to non existent memory (like Bar & does). It also makes ownership explicit.

I generally don't like handing references to internal members. What happens if another thread modifies it? Instead if I cant hand out a copy, I prefer a more functional approach. Something like this.

class Foo {
private:
    std::map<std::string, std::unique_ptr<Bar> > bars;

    // ...

public:

    // ...
    template<typename Callable> void withBar(const std::string& name, Callable call) const {
        //maybe a lock_guard here?
        auto iter = bars.find(name);
        if(iter != std::end(bars)) {
            call(iter->second.get());
        }
    }
}

This way the owned internal never "leaves" the class that owns it, and the owner can control invariants. Also can have niceties like making the code a noop if the requested entry doesn't exist. Can use it like,

myfoo.withBar("test", [] (const Bar* bar){
   bar->dostuff();
});

If possible, I would try to avoid exposing the internals of Document at all but failing that I would either return a const Field& or if you need it to be nullable a const Field*. Both clearly show that Document is retaining ownership. Also, make the method itself const.

You could have a non const version of the method that returns a Field& or Field* but I would avoid that if you can.

This is not yet considered, I guess: return a proxy object with the same interface.

The holder of the proxy object has the ownership of that proxy-object. The proxy holds whatever reference(weak maybe) to the referenced object. When the object is deleted then you can raise some error. Th eproxy object does not have ownership.

Maybe it was already mentioned. I am not so familiar with C++.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!