C ++ метод, который может / не может вернуть структуру - PullRequest
8 голосов
/ 13 декабря 2010

У меня есть структура C ++ и метод:

struct Account
{
    unsigned int id;
    string username;
    ...
};


Account GetAccountById(unsigned int id) const { }

Я могу вернуть структуру Account, если учетная запись существует, но что делать, если учетной записи нет?

Я думал о том, чтобы иметь:

  • Флаг «действителен» в структуре (поэтому может быть возвращен пустой флаг, для которого установлено значение false)
  • Дополнительный указатель «действителен» (const string & id, int * is_ok), который устанавливается, если вывод действителен
  • Взамен возврата учетной записи * и возврата либо указателя на структуру, либо NULL, если она не существует?

Есть ли лучший способ сделать это?

Ответы [ 11 ]

13 голосов
/ 13 декабря 2010

Вы забыли самый очевидный в C ++:

bool GetAccountById(unsigned int id, Account& account);

Верните true и заполните предоставленную ссылку, если учетная запись существует, иначе верните false.

Itтакже может быть удобно использовать тот факт, что указатели могут быть нулевыми и иметь:

bool GetAccountById(unsigned int id, Account* account);

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

Это вопрос вкуса, который вы предпочитаете иметь.

7 голосов
/ 13 декабря 2010

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

Другая возможность - throw исключение, когда такой учетной записи нет.Вы также можете попробовать boost::optional.

6 голосов
/ 13 декабря 2010

Вы также можете попробовать шаблон нулевого объекта .

3 голосов
/ 13 декабря 2010

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

Если он действительно исключительный - глубоко в недрах внутренней системы банковской системы, где данные должны быть действительными- тогда, может быть, выдать исключение.

Если проверка данных выполняется на уровне пользовательского интерфейса, то, вероятно, вы не выбросите исключение.

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

Можете ли вы использовать «идентификатор маркера» (например, 0) для обозначения «неверной учетной записи»?

2 голосов
/ 13 декабря 2010

Есть несколько методов.

1) Брось исключение. Это полезно, если вы хотите, чтобы GetAccountById возвращал учетную запись по значению , а использование исключений соответствует вашей модели программирования. Некоторые скажут вам, что исключения «предназначены» для использования только в исключительных случаях. Такие вещи, как «не хватает памяти» или «компьютер в огне». Это очень спорно, и для каждого программиста вы найдете, кто говорит, что исключения не для управления потоком данных вы найдете другие (я в том числе), который говорит, что исключения могут быть использованы для управления потоком. Вам нужно подумать об этом и решить для себя.

Account GetAccountById(unsigned int id) const
{
  if( account_not_found )
    throw std::runtime_error("account not found");
}

2) Не возвращать и Account по значению. Вместо этого вернитесь по указателю (желательно умному указателю) и верните NULL, если вы не нашли учетную запись:

boost::shared_ptr<Account> GetAccountById(unsigned int id) const
{
  if( account_not_found )
    return NULL;
}

3) Вернуть объект с флагом присутствия, указывающим, присутствует ли элемент данных. Boost.Optional является примером такого устройства, но в случае, если вы не можете использовать Boost, это шаблонный объект, который имеет bool член, равный true, когда присутствует элемент данных, и false, когда это не так. Сам элемент данных хранится в элементе value_. Должно быть конструируемым по умолчанию.

template<class Value>
struct PresenceValue 
{
    PresenceValue() : present_(false) {};
    PresenceValue(const Value& val) : present_(true), value_(val) {};
    PresenceValue(const PresenceValue<Value>& that) : present_(that.present_), value_(that.value_) {};
    explicit PresenceValue(Value val) : present_(true), value_(val) {};
    template<class Conv> explicit PresenceValue(const Conv& conv) : present_(true), value_(static_cast<Value>(conv)) {};
    PresenceValue<Value>& operator=(const PresenceValue<Value>& that) { present_ = that.present_; value_ = that.value_; return * this; }

    template<class Compare> bool operator==(Compare rhs) const
    {
        if( !present_ )
            return false;
        return rhs == value_;
    }
    template<class Compare> bool operator==(const Compare* rhs) const
    {
        if( !present_ )
            return false;
        return rhs == value_;
    }
    template<class Compare> bool operator!=(Compare rhs) const { return !operator==(rhs); }
    template<class Compare> bool operator!=(const Compare* rhs) const { return !operator==(rhs); }

    bool operator==(const Value& rhs) const { return present_ && value_ == rhs; }
    operator bool() const { return present_ && static_cast<bool>(value_); }

    operator Value () const;

    void Reset() { value_ = Value(); present_ = false; }

    bool present_;
    Value value_;
};

Для простоты я бы создал typedef для Account:

typedef PresenceValue<Account> p_account;

... и затем верните это из вашей функции:

p_account GetAccountByIf(...)
{
  if( account_found )
    return p_account(the_account); // this will set 'present_' to true and 'value_' to the account
  else
    return p_account(); // this will set 'present_' to false
}

Использовать это просто:

p_account acct = FindAccountById(some_id);
if( acct.present_ )
{
  // magic happens when you found the account
}
2 голосов
/ 13 декабря 2010

Я бы использовал Account* и добавил бы к документации комментарий к методу о том, что возвращаемое значение может быть NULL.

0 голосов
/ 13 декабря 2010

Мне нравится делать комбинацию того, что вы предлагаете с флагом Valid, и того, что кто-то еще предлагал с шаблоном нулевого объекта.

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

class Status
{
   public:
     Status(bool isOK=true) : mIsOK(isOK)
     operator bool() {return mIsOK;}
   private
     bool mIsOK
};

теперь у вас есть

class Account : public Status
{
   public:
     Account() : Status(false)
     Account(/*other parameters to initialize an account*/) : ...
     ...
};

Теперь, если вы создаете учетную запись без параметров:

Account A;

Это недействительно. Но если вы создадите аккаунт с данными

Account A(id, name, ...);

Это действительно.

Вы проверяете действительность с оператором bool.

Account A=GetAccountByID(id);
if (!A)
{
   //whoa there!  that's an invalid account!
}

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

bool Matrix_Multiply(a,b,c);

где a, b и c - матрицы. Я бы лучше написал

c=a*b;

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

c=a*b;
if (!c) //handle the problem.
0 голосов
/ 13 декабря 2010

Я бы сделал:

class Bank
{
  public:

    class Account {};
    class AccountRef
    {
        public:
            AccountRef():                   m_account(NULL)  {}
            AccountRef(Account const& acc)  m_account(&acc)  {}
            bool isValid() const                             { return m_account != NULL);}
            Account const& operator*()                       { return *m_account; }
            operator bool()                                  { return isValid(); }
        private:
            Account const* m_account;
    }; 
    Account const& GetAccountById(unsigned int id) const
    {
        if (id < m_accounts.size())
        {    return m_accounts[id];
        }
        throw std::outofrangeexception("Invalid account ID");
    }

    AccountRef FindAccountById(unsigned int id) const
    {
        if (id < m_accounts.size())
        {    return AccountRef(m_accounts[id]);
        }
        return AccountRef();
    }
  private:
    std::vector<Account>   m_accounts;
};

Метод с именем get всегда должен возвращать (IMHO) запрашиваемый объект.Если он не существует, то это исключение.Если существует вероятность того, что что-то может не существовать, вы должны также предоставить метод find, который может определить, существует ли объект, чтобы пользователь мог проверить его.

int main()
{
    Bank    Chase;

    // Get a reference
    // As the bank ultimately ownes the account.
    // You just want to manipulate it.
    Account const&   account = Chase.getAccountById(1234);

    // If there is the possibility the account does not exist then use find()
    AccountRef ref = Chase.FindAccountById(12345);
    if ( !ref )
    {     // Report error
          return 1;
    }
    Account const&  anotherAccount = *ref;
}

Теперь я мог бы вместо этого использовать указательидти на усилия по созданию AccountRef.Проблема заключается в том, что указатели не имеют синтаксиса владения, и, следовательно, нет точного указания на то, кто должен владеть (и, следовательно, удалять) указатель.

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

Здесь вы можете проверить, действителен ли AccountRef, и извлечь ссылку на учетную запись (при условии, что это действительно).Поскольку объект содержит только указатель, компилятор должен оптимизировать его до такой степени, что это не дороже, чем передавать указатель.Преимущество заключается в том, что пользователь не может случайно злоупотребить тем, что я ему дал.

Резюме: AccountRef не имеет реальных затрат времени выполнения.Тем не менее обеспечивает безопасность типов (поскольку скрывает использование указателя).

0 голосов
/ 13 декабря 2010

Существует еще один способ, похожий на шаблон «является действительным».Сейчас я занимаюсь разработкой приложения, в котором много таких вещей.Но мои идентификаторы никогда не могут быть меньше 1 (все они являются полями SERIAL в базе данных PostgreSQL), поэтому у меня просто есть конструктор по умолчанию для каждой структуры (или класса в моем случае), который инициализирует id методом -1 и isValid()возвращает true, если id не равно -1.У меня отлично работает.

0 голосов
/ 13 декабря 2010

boost :: option - это, вероятно, лучшее, что вы можете сделать на таком сломанном языке, у которого нет нативных вариантов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...