В моем классе есть методы, которые возвращают const <Container>&
потому что я не хочу, чтобы возвращаемый контейнер был изменен снаружи, и копирование может быть дорогим. например: const std::set<ClassA>& foo()
и я хочу foo()
чтобы иметь возможность вернуть константную ссылку на пустую std::set<ClassA>
если это должно. то есть
const std::set<ClassA>& foo(const std::string& key) {
std::map<std::string, std::set<ClassA>>::iterator itr = m_elements.find(key);
return (itr != m_elements.end()) ? *itr : /*empty std::set<ClassA>*/;
}
Но я не могу вернуть константную ссылку на временно созданную пустую std::set<ClassA>
в foo()
, Чтобы решить эту проблему, я определяю общий шаблонный синглтон-класс в общем месте, чтобы его можно было использовать с любым типом
template <typename T> class cNull
{
public:
static const T& Value() {
static cNull<T> instance;
return instance.d;
}
private:
cNull() {};
cNull(const cNull<T>& src);
void operator=(cNull<T> const&);
T d;
};
А сейчас foo()
может быть что-то вроде
const std::set<ClassA>& foo(const std::string& key) {
std::map<std::string, std::set<ClassA>>::iterator itr = m_elements.find(key);
return (itr != m_elements.end()) ? *itr : cNull<std::set<ClassA> >.Value();
}
Мне было интересно, есть ли лучший способ решить эту проблему и есть ли какие-либо проблемы с этим дизайном?
Мне было интересно, есть ли лучший способ решить эту проблему и есть ли какие-либо проблемы с этим дизайном?
Конечно, есть: не возвращайте ссылку, а копию. Когда вы говорите, что «вы хотите иметь возможность вернуть пустую std::set<T>
«Вы просто заявляете, что можете изменить возвращаемое значение относительно его исходного состояния (в качестве переменной-члена). В этом случае копия вполне подойдет.
В такой ситуации вы обычно две альтернативы:
boost::optional
который может быть содержать найденный элемент.Немного зависит от того, почему вы возвращаете ссылку.
Если это потому, что вызывающая сторона ожидает сохранить ссылку и отразить в ней изменения, сделанные через объект, для которого foo
что произойдет, если вы вернете ссылку на ваш пустой набор синглтона, и тот же ключ будет позже добавлен в m_elements
? Ссылка не делает то, что рекламируется. Может быть, лучше добавить пустой набор на карту, когда foo
называется, поэтому код становится:
const std::set<int>& foo(const std::string& key) {
return m_elements[key];
}
Если вы возвращаете ссылку исключительно по соображениям производительности (чтобы избежать потенциально дорогой копии большого набора), а не потому, что вам действительно нужна семантика ссылки на часть объекта, то возврат статического пустого набора будет работать, и я полагаю, нет ничего плохого в том, чтобы иметь помощника шаблона для его достижения, если вы обнаружите, что часто делаете одно и то же с несколькими типами. Будьте очень осторожны, чтобы документально подтвердить, что ссылка вернулась может или не может отражать дальнейшие изменения объекта (в зависимости от того, key
присутствовал когда foo
был вызван, хотя вы не могли бы гарантировать, что). Тогда вызывающие абоненты будут знать, чтобы не использовать его после даты его продажи, то есть всякий раз, когда кто-либо в следующий раз вносит соответствующее изменение в объект.
Если вы не знаете, почему вы возвращаете ссылку, либо верните копию, либо выясните, что ожидают вызывающие абоненты. делать с этим набором, и заменить foo
с одной или несколькими функциями, которые это делают. Таким образом, вы не позволяете ссылкам на внутренности вашего класса попадать в руки пользователей.
Я предположил, что пустой набор верен по какой-то веской причине — возможно, чтобы избежать того, чтобы каждый вызывающий объект проверял возвращаемое значение и / или проверял существование key
перед звонком foo
,
Есть несколько вариантов сделать это, но прежде чем вы сможете выбрать один, вы должны ответить: мне действительно нужно вернуть ссылку?
Если вы пытаетесь воздействовать на результат (и хотите, чтобы он был отражен в изменениях — и не можете изменить интерфейс), ответ — да. Если какое-либо из этих условий изменится, вы можете вернуться копированием, что значительно облегчит это:
std::set<int> foo(const std::string& key) const
{
std::set<int> results;
std::map<std::string, std::set<int>>::iterator it = m_elements.find(key); // assuming m_elements is std::map<std::string, std::set<int>>
if (it != m_elements.end())
{
results = it->second;
}
return results;
}
Если вы должны вернуть ссылку, вы можете сделать что-то вроде boost::optional
(или использовать std::pair
для имитации), бросить исключение, иметь внутренний нуль-набор. Опция, наиболее близкая к тому, что вы пытаетесь сделать, — это необязательный / парный подход:
std::pair<bool, std::set<int>*> foo(const std::string& key)
{
std::pair<bool, std::set<int>*> results = std::make_pair(false, nullptr);
std::map<std::string, std::set<int>>::iterator it = m_elements.find(key);
if (it != m_elements.end())
{
results.first = true;
results.second = &(it->second);
}
return results;
}
Затем вы можете проверить логическое значение (гарантированно действительное), чтобы увидеть, является ли значение указателя допустимым. boost::optional
делает что-то подобное (и если вы можете использовать boost, предпочитайте это, используя std::pair
версия для симуляции).
Я бы вернул указатель или nullptr для обозначения не найдено.
const std::set<ClassA>* foo(const std::string& key)
{
auto it = m_elements.find(key);
if (it == m_elements.end()) return NULL;
return &(*it);
}
просто, и он не страдает от синглтонита.