I have a class that stores & manages a vector containing a number of objects.
I'm finding myself writing a number of functions similar to the following:
Object* ObjectManager::getObject(std::string name){
for(auto it = object_store.begin(); it != object_store.end(); ++it){
if(it->isCalled(name))
return &(*it)
}
return nullptr;
}
I think I would rather return by reference, as here the caller would have to remember to check for null! Is there a way I can change my design to better handle this?
答案 0 :(得分:8)
Your alternatives are outlined below
Change your API to the following
object_manager.execute_if_has_object("something", [](auto& object) {
use_object(object);
});
This API is much easier to use, conveys intent perfectly and removes the thought process of error handling, return types, etc from the user's mind
Throw an exception.
Object& ObjectManager::getObject(const std::string& name){
for(auto& object : object_store){
if(object.isCalled(name))
return object;
}
// throw an exception
throw std::runtime_error{"Object not found"};
}
Return a bool, pass the Object
by reference and get a copy
bool ObjectManager::getObject(const std::string& name, Object& object_out){
for(auto& object : object_store){
if(object.isCalled(name)) {
object_out = object;
return true;
}
}
return false;
}
Let the user do the finding
auto iter = std::find(object_store.begin(), object_store.end(), [&name](auto& element) {
return element.isCalled(name);
}
if (iter != object_store.end()) { ... }
Also
std::string_view
答案 1 :(得分:3)
Look at the design of STL (e.g. find
function), it is not at all bad to return the iterator your just searched for, and return .end()
otherwise.
auto ObjectManager::getObject(std::string name){
for(auto it = object_store.begin(); it != object_store.end(); ++it){
if(it->isCalled(name))
return it;
}
return object_store.end();
}
More: Of course object_store.end()
may be inaccessible from outside the class but that is not an excuse, because you can do this (note the more slick code also)
auto ObjectManager::getObject(std::string name){
auto it = object_store.begin();
while(not it->isCalled(name)) ++it;
return it;
}
auto ObjectManager::nullObject(){return object_store.end();}
Less code is better code. You can use it like this:
auto result = *om.getObject("pizza"); // search, not check (if you know what you are doing)
or
auto it = om.getObject("pizza");
if(it != om.nullObject() ){ ... do something with *it... }
or
auto it = om.getObject("pizza");
if(it != om.nullObject() ){ ... do something with *it... }
else throw java_like_ridiculous_error("I can't find the object, the universe will collapse and it will jump to another plane of existence");
Of course at this point it is better to call the functions findOject
and noposObject
and also question why not using directly std::find
on the object_store
container.
答案 2 :(得分:1)
替代异常或可选的解决方案是实现一个" Null object" - 它可以用作常规对象,但不会做什么"。取决于案例,有时它可以按原样使用,不需要(明确地)检查 - 特别是在忽略"未找到"情况是可以接受的。
(null对象可以是静态全局,因此也可以返回对它的引用)
即使需要检查,也可以实现isNull()
方法,对于null对象返回true,对有效对象返回false(或者可以有isValid()
方法等)。
示例:
class Object {
public:
virtual void doSomething();
};
class NullObject: public Object {
public:
virtual void doSomething() {
// doing nothing - ignoring the null object
}
};
class ObjectManager {
public:
Object& getObject(const std::string& name);
private:
static NullObject s_nullObject;
};
Object& ObjectManager::getObject(const std::string& name){
for(auto it = object_store.begin(); it != object_store.end(); ++it){
if(it->isCalled(name))
return *it;
}
return s_nullObject;
}
ObjectManager mgr;
Object& obj = mgr.getObject(name);
obj.doSomething(); // does nothing if the object is NullObject
// (without having to check!)
答案 3 :(得分:1)
我认为您已经正确处理了返回值,并且您当前的解决方案是最佳的。
事实上,您无法避免检查某些内容,以便发现您的查找操作是否成功。如果您抛出异常,那么您的try{}catch{}
就是您的支票。如果找不到合法的结果,也不应使用exception
。如果您返回bool
并使用 out参数,则表明您执行相同工作的情况会更复杂。与返回迭代器相同。 std::optional
返回值。
因此,IMO在返回指针时无法改进,您可以让同样的工作更复杂。