Как я должен вернуть объект из функции?

Рассмотрим следующий сценарий: есть класс CDriver отвечает за перечисление всех подключенных выходных устройств (представленных COutput учебный класс). Код для этого может выглядеть примерно так:

class COutput
{
// COutput stuff
};

class CDriver
{
public:
CDriver();  // enumerate outputs and store in m_outputs

// some other methods

private:
std::vector<COutput> m_outputs;
};

Сейчас CDriver должен иметь возможность предоставить пользователю доступ к перечисленным COutputs.

Первый способ достижения этого — вернуть указатель:

const COutput* GetOutput(unsigned int idx) const
{
return idx < m_outputs.size() ? &m_outputs[idx] : nullptr;
}

На мой взгляд, этот метод представляет проблему, если указатель сохраняется пользователем и сохраняется после CDriver объект был уничтожен, теперь он является висящим указателем. Это связано с тем, чтоCOutput объект) был разрушен во время деструктора CDriver объект.

Второй способ сделать это будет вернуться по ссылке:

const COutput& GetOutput(unsigned int idx) const
{
return idx < m_outputs.size() ? &m_outputs[idx] : m_invalidOutput;
}

Здесь применяются те же проблемы, что и в подходе с указателями. Кроме того, он имеет дополнительное предостережение о том, что реальный недопустимый объект не может быть возвращен. Если nullptr возвращается как указатель возврата, очевидно, что он «недействителен». Однако нет эквивалента nullptr когда дело доходит до ссылок.

Двигаясь к третьему. Возврат по значению.

COutput GetOutput(unsigned int idx) const
{
return idx < m_outputs.size() ? &m_outputs[idx] : m_invalidOutput;
}

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

Я мог бы продолжить …

Например, COutput объекты могут быть размещены в куче и сохранены в std::shared_ptrи вернулся как таковой. Это, однако, сделает код очень многословным.

Есть ли способ решить эту проблему интуитивно и без введения ненужного многословия кода?

3

Решение

Позвольте мне начать с того, что вы абсолютно не должны возиться с shared_ptr Для решения этой проблемы. Просто не делай этого. У вас есть несколько вариантов, которые являются разумными.

Во-первых, вы можете просто вернуть по значению. Если COutput маленький, это хороший путь. Чтобы справиться с индексом вне границ, у вас есть два варианта. Одним из них является исключение. Работает хорошо и легко. Это то, что я бы порекомендовал здесь, скорее всего. Убедитесь, что есть size() член, которому пользователь может позвонить, чтобы получить размер, чтобы они могли избежать оплаты броска, если это слишком дорого для них. Вы также можете вернуть optional, Это в стандартной библиотеке по состоянию на 17, в повышении до, и есть автономные реализации.

Во-вторых, вы можете вернуться по указателю / ссылке. Да, это может болтаться. Но C ++ не претендует на защиту от этого. Каждый стандартный контейнер имеет begin() а также end() методы, которые возвращают итераторы, также могут легко свисать. Ожидание того, что клиенты избежат этих ловушек, не является необоснованным в C ++ (хотя вы, конечно, должны их документировать).

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

template <class F>
auto apply(std::size_t idx, F f) const
{
if (idx >= m_outputs.size())
{
throw std::out_of_range("index is out of range");
}

return f(m_outputs[idx]);
}

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

CDriver x;
x.apply(3, [] (const COutput& o) {
o.do_something();
});

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

Вы можете, конечно, изменить apply во многих отношениях; например не возвращать обратно из функционального вызова, но вместо этого возвращать true / false, чтобы указать, был ли индекс в диапазоне вместо броска. Основная идея та же самая. Обратите внимание, что этот подход должен быть изменен для использования в сочетании с виртуальными функциями, что сделает его менее желательным. Так что, если вы думаете о полиморфизме для CDriver, вы должны учитывать это.

5

Другие решения

Взгляните на общие указатели C ++ 11. При использовании общих указателей деконструктор базового объекта не будет вызываться до тех пор, пока не будут уничтожены все общие указатели, «владеющие» этим объектом. Это устраняет большую (но не всю) головную боль при работе с несколькими ссылками на один объект.

Вот еще немного информации:
http://en.cppreference.com/w/cpp/memory/shared_ptr

0

1). Испытано и проверено: киньте стандарт исключение аргумента

2) Вы можете использовать кортежи и станд :: галстука.

const std::tuple<bool, COutput> GetOutput(unsigned int idx) const
{
return idx < m_outputs.size()
? std::make_tuple(true m_outputs[idx])
: std::make_tuple(false,  m_invalidOutput);
}

bool has_value;
COutput output;

std::tie(has_value, output) = GetOutput(3);

Заменить кортежи и std :: tie в C ++ 17 структурированные привязки может быть использован.

3) C ++ 17 будет иметь станд :: опционально для такого рода сценария.

0
По вопросам рекламы [email protected]