How to manage & ldquo; Item not found & rdquo; Situations in a search function?

I'm frequently run into a situation where I need to report in some way that a finding an item has failed. Since there are many ways how to deal with such a situation I'm always unsure how to do it. Here are a few examples:

class ItemCollection
{
public:

    // Return size of collection if not found.
    size_t getIndex(Item * inItem)
    {
        size_t idx = 0;
        for (; idx != mItems.size(); ++idx)
        {
            if (inItem == mItems[idx])
            {
                return idx;
            }
        }
        return idx;
    }

    // Use signed int and return -1 if not found.
    int getIndexV2(Item * inItem)
    {
        for (int idx = 0; idx != mItems.size(); ++idx)
        {
            if (inItem == mItems[idx])
            {
                return idx;
            }
        }
        return -1;
    }

    // Throw exception if not found.
    size_t getIndexV3(Item * inItem)
    {
        for (size_t idx = 0; idx != mItems.size(); ++idx)
        {
            if (inItem == mItems[idx])
            {
                return idx;
            }
        }
        throw std::runtime_error("Item not found");
    }

    // Store result in output parameter and return boolean to indicate success.
    bool getIndex(Item * inItem, size_t & outIndex)
    {
        for (size_t idx = 0; idx != mItems.size(); ++idx)
        {
            if (inItem == mItems[idx])
            {
                outIndex = idx;
                return true;
            }
        }
        return false;
    }

private:
    std::vector<Item*> mItems;
};

I've used all of these at some point in my (young) programming carreer. I mostly use the "return size of collection" approach because it is similar to how STL iterators work. However, I'd like to make more educated choices in the future. So, on what design principles should the decision on how to deal with not-found errors be based?


Your functions are more like std::string::find than any of the iterator-based functions in the algorithm header. It returns an index, not an iterator.

I don't like that your function returns the collection size to emulate "one past the end." It requires the caller to know the collection size in order to check whether the function succeeded. I like your second function better since it returns a single constant value that always means "not found." The std::string type combines both of those by returning std::string::npos, which has a value of -1, but as an unsigned type.

Stay away from the exception approach of your third function unless you have some other function that call tell in advance whether the item would be found. That is, provide some way for callers to avoid the exception.

Your fourth function is most appropriate when the returned index would be useful even when the item isn't found. If you were doing a binary search, it could be useful to know the index where the item would be found if it were in the collection. Then you could provide an insert function that accepts that value as a hint, just like std::map::insert. If you can't provide that kind of information, then don't use that kind of function since it's just more cumbersome for callers to use. Prefer your first style instead.