Что такое эквивалент не указателя NULL?

Извините за мою неправильную терминологию.

У меня есть кусок кода, который возвращает указатель NULL, если запись не существует:

ObjectType * MyClass::FindObjectType( const char * objectTypeName )
{
    if ( objectTypeMap.find( objectTypeName ) == objectTypeMap.end() )
    {
        Msg( "\n[C++ ERROR] No object type: %s", objectTypeName );
        return NULL;
    }
    else
        return &objectTypeMap[ objectTypeName ];
}

Я хочу сделать то же самое, но на этот раз вернуть объект вместо простого указателя. Следующий код не дает мне никаких ошибок компилятора (что меня удивляет):

ObjectType MyClass::FindObjectType( const char * objectTypeName )
{
    if ( objectTypeMap.find( objectTypeName ) == objectTypeMap.end() )
    {
        Msg( "\n[C++ ERROR] No object type: %s", objectTypeName );
    }
    else
        return objectTypeMap[ objectTypeName ];
}

С помощью указателя я могу проверить, не была ли запись такой:

if ( FindObjectType( objectType ) == NULL )
    //Do something

Как выполнить эквивалентную проверку с возвращаемым объектом?

Ответ 1

Для объектов нет эквивалента уровня языка.

Один из вариантов заключается в создании объекта "дозорного", который гарантированно сравнится неравномерно с любым "реальным" объектом и возвращает его:

class ObjectType {
public:
    static const ObjectType null;

    bool operator==(const ObjectType &rhs) const { /* need an appropriate comparison test */ }

    ...
};

ObjectType ObjectType::null(/* something unique */);


...

ObjectType foo(const char *objectTypeName) {
    if (cond) {
        return objectTypeMap[objectTypeName];
    } else {
        return ObjectType::null;
    }
}


...

if (foo(objectType) == ObjectType::null) {
    std::cout << "Returned the null object\n";
}

Ответ 2

Следующий код не дает ошибку, потому что стандарт очень консервативен.

Некоторые структуры кода чрезвычайно запутаны, и компилятор не может знать, может ли быть достигнут конец функции или нет. Поэтому в Стандарте говорится, что компилятор не должен доказывать, что функция правильно возвращает значение...

Однако Стандарт говорит, что если функция заканчивается нормально (без исключения), не возвращая значения, тогда вызывается Undefined Behavior (т.е. что угодно может произойдет, вероятно, сбой). Поэтому большинство компиляторов имеют предупреждение для такой ситуации, для gcc и Clang вы можете использовать -Wreturn.


Теперь принцип значений nullity или sentinel не является новым, а нулевой указатель - это всего лишь одно воплощение (среди многих).

Если для вашего объекта не имеет смысла быть нулевым (это редко бывает, но может быть целесообразным), тогда у вас есть 2 альтернативы:

  • throw исключение для сообщения об ошибке
  • возвращает класс оболочки (например, boost::optional<ObjectType>), который может иметь значение null

В этом условии, так как ожидается, что Find может ничего не найти, я бы посоветовал последнее в целом.

Использование прост:

boost::optional<ObjectType> MyClass::FindObjectType(char const* objectTypeName )
{
    if ( objectTypeMap.find( objectTypeName ) == objectTypeMap.end() ) {
        // do not print anything, it is up to the caller to decide what to do
        return boost::none;
    }

    return objectTypeMap[ objectTypeName ];
}

И затем вызывающий абонент пишет:

int main(int argc, char* argv[]) {
    if (boost::optional<ObjectType> o = MyClass::FindObject(argv[1])) {
        o->foo();

        return 0;
    }

    Msg( "\n[C++ ERROR] No object type: %s", argv[1]);
    return 1;
}