Подтвердить что ты не робот

Возвращает ссылку "NULL" в С++?

В динамически типизированных языках, таких как JavaScript или PHP, я часто выполняю такие функции, как:

function getSomething(name) {
    if (content_[name]) return content_[name];
    return null; // doesn't exist
}

Я возвращаю объект, если он существует, или null, если нет.

Каким будет эквивалент в С++ с использованием ссылок? Есть ли рекомендованная модель в целом? Я видел некоторые рамки, имеющие метод isNull() для этой цели:

SomeResource SomeClass::getSomething(std::string name) {
    if (content_.find(name) != content_.end()) return content_[name];
    SomeResource output; // Create a "null" resource
    return output;
}

Затем вызывающий может проверить ресурс таким образом:

SomeResource r = obj.getSomething("something");
if (!r.isNull()) {
    // OK
} else {
    // NOT OK
}

Однако, для реализации такого типа магического метода для каждого класса кажется тяжелым. Также не кажется очевидным, когда внутреннее состояние объекта должно быть установлено с "null" на "not null".

Есть ли альтернатива этому шаблону? Я уже знаю, что это можно сделать с помощью указателей, но мне интересно, как это сделать, если это можно сделать со ссылками. Или я должен отказаться от возвращения "нулевых" объектов в С++ и использовать некоторый С++-специфический шаблон? Любое предложение о правильном способе сделать это будет оценено.

4b9b3361

Ответ 1

Вы не можете делать это во время ссылок, так как они никогда не должны быть NULL. Существуют три варианта: один использует указатель, другие используют семантику значения.

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

    SomeResource* SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return &(*it);  
        return NULL;  
    }
    
  • Используя std::pair с bool, чтобы указать, является ли элемент действительным или нет (обратите внимание: требуется, чтобы SomeResource имел соответствующий конструктор по умолчанию и не дорого стоить):

    std::pair<SomeResource, bool> SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return std::make_pair(*it, true);  
        return std::make_pair(SomeResource(), false);  
    }
    
  • Используя boost::optional:

    boost::optional<SomeResource> SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return *it;  
        return boost::optional<SomeResource>();  
    }
    

Если вам нужна семантика значения и у вас есть возможность использовать Boost, я бы рекомендовал вариант три. Основным преимуществом boost::optional over std::pair является то, что униализированное значение boost::optional не создает тип его инкапсуляции. Это означает, что он работает для типов, которые не имеют конструктора по умолчанию и сохраняет время/память для типов с нетривиальным конструктором по умолчанию.

Я также изменил ваш пример, чтобы вы не дважды искали карту (повторное использование итератора).

Ответ 2

Почему "помимо использования указателей"? Использование указателей , как вы это делаете в С++. Если вы не определите какой-то "необязательный" тип, который имеет что-то вроде функции isNull(), которую вы упомянули. (или использовать существующий, например boost::optional)

Ссылки разработаны и гарантированы, чтобы никогда не быть нулевыми. Запрашивать "так, как я могу сделать их null", бессмысленно. Вы используете указатели, когда вам нужна "нулевая ссылка".

Ответ 3

Один хороший и относительно неинтрузивный подход, который позволяет избежать проблемы при реализации специальных методов для всех типов, используется с boost.optional, Это, по сути, обертка шаблона, которая позволяет вам проверить, является ли допустимое значение "действительным" или нет.

Кстати, я думаю, что это хорошо объяснено в документах, но остерегайтесь boost::optional of bool, это сложная интерпретация.

Изменить. Вопрос задает "Ссылка NULL", но фрагмент кода имеет функцию, которая возвращает значение. Если эта функция действительно вернула ссылку:

const someResource& getSomething(const std::string& name) const ; // and possibly non-const version

тогда функция имела бы смысл, если бы упомянутый someResource имел время жизни, по крайней мере, до тех пор, пока объект, возвращающий ссылку (в противном случае вы получите dwave ссылку). В этом случае кажется прекрасным возвращать указатель:

const someResource* getSomething(const std::string& name) const; // and possibly non-const version

но вы должны сделать его абсолютно понятным, чтобы вызывающий пользователь не владел указателем и не пытался его удалить.

Ответ 4

Я могу придумать несколько способов справиться с этим:

  • Как и другие, используйте boost::optional
  • Сделать объектом состояние, которое указывает, что оно недействительно (Yuk!)
  • Использовать указатель вместо ссылки
  • Имейте специальный экземпляр класса, который является нулевым объектом
  • Выбросить исключение, чтобы указать отказ (не всегда применимый)

Ответ 5

в отличие от Java и С# в ссылочном объекте С++ не может быть нулевым.
поэтому я бы посоветовал два метода, которые я использую в этом случае.

1 - вместо ссылки используйте тип с нулевым значением, например std:: shared_ptr

2 - получить ссылку как параметр out и возвратить Boolean для успеха.

bool SomeClass::getSomething(std::string name, SomeResource& outParam) {
    if (content_.find(name) != content_.end()) 
    {
        outParam = content_[name];
        return true;
    }
    return false;
}

Ответ 6

Вот несколько идей:

Альтернатива 1:

class Nullable
{
private:
    bool m_bIsNull;

protected:
    Nullable(bool bIsNull) : m_bIsNull(bIsNull) {}
    void setNull(bool bIsNull) { m_bIsNull = bIsNull; }

public:
    bool isNull();
};

class SomeResource : public Nullable
{
public:
    SomeResource() : Nullable(true) {}
    SomeResource(...) : Nullable(false) { ... }

    ...
};

Альтернатива 2:

template<class T>
struct Nullable<T>
{
    Nullable(const T& value_) : value(value_), isNull(false) {}
    Nullable() : isNull(true) {}

    T value;
    bool isNull;
};

Ответ 7

Этот код ниже показывает, как вернуть "недопустимые" ссылки; это просто другой способ использования указателей (обычный метод).

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

#include <iostream>
#include <cstddef>

#define Nothing(Type) *(Type*)nullptr
//#define Nothing(Type) *(Type*)0

struct A { int i; };
struct B
{
    A a[5];
    B() { for (int i=0;i<5;i++) a[i].i=i+1; }
    A& GetA(int n)
    {
        if ((n>=0)&&(n<5)) return a[n];
        else return Nothing(A);
    }
};

int main()
{
    B b;
    for (int i=3;i<7;i++)
    {
        A &ra=b.GetA(i);
        if (!&ra) std::cout << i << ": ra=nothing\n";
        else std::cout << i << ": ra=" << ra.i << "\n";
    }
    return 0;
}

Макрос Nothing(Type) возвращает значение, в этом случае представленное nullptr - вы также можете использовать 0, к которому задан ссылочный адрес. Этот адрес теперь можно проверить как - если вы используете указатели.

Ответ 8

Еще один вариант - тот, который я использовал время от времени, когда вы действительно не хотите, чтобы возвращался "нулевой" объект, а вместо этого подойдет "пустой/недействительный" объект:

// List of things
std::vector<some_struct> list_of_things;
// An emtpy / invalid instance of some_struct
some_struct empty_struct{"invalid"};

const some_struct &get_thing(int index)
{
    // If the index is valid then return the ref to the item index'ed
    if (index <= list_of_things.size())
    {
        return list_of_things[index];
    }

    // Index is out of range, return a reference to the invalid/empty instance
    return empty_struct; // does not exist
}

Это довольно просто и (в зависимости от того, что вы делаете с ним на другом конце) может избежать необходимости выполнять проверки нулевого указателя на другой стороне. Например, если вы генерируете несколько списков вещей, например:

for (const auto &sub_item : get_thing(2).sub_list())
{
    // If the returned item from get_thing is the empty one then the sub list will
    // be empty - no need to bother with nullptr checks etc... (in this case)
}