У меня есть функция, использующая double *
void funct(double*);
и у меня есть вектор:
std::vector<double> myVec;
как правильно и безопасно позвонить funct (myVec)?
не безопасно:
funct(&myVec[0]);
funct(&*myVec.begin());
не приятно читать:
funct( myVec.empty()? NULL : &myVec[0]);
funct( myVec.empty()? NULL : &*myVec.begin());
любое предложение?
Какой стандартный подход?
Ну, стандартный тип типа std::vector
имеет функцию-член, называемую data
, которая должна возвращать указатель на базовое хранилище. По-видимому, data()
- это не более &front()
с гарантией того, что:
Указатель таков, что диапазон
[data(); data() + size())
[data(); data() + size())
всегда является допустимым диапазоном, даже если контейнер пуст.
Поэтому я бы сказал, что оба:
funct(vector.data());
funct(&vector.front());
можно безопасно использовать.
Но реальный вопрос: что вы пытаетесь сделать внутри функции?
Я вижу 3 очевидных ответа на это, и я собираюсь предложить альтернативы для всех:
Начнем с первого, не так ли? Если вам нужен только элемент массива, зачем беспокоиться о указателях и массиве вообще? Вы можете просто использовать:
void funct(double);
и покончить с этим. И если вы хотите изменить этот double
, почему бы не передать его по ссылке?
void funct(double&);
и вызовите функцию как:
funct(vector[0]);
Номер два имеет два очень возможных ответа. Один из них - использовать перегрузку функции следующим образом:
void funct();
void funct(double);
И в основном рассмотрим функцию без аргумента и аргумента. Самое простое решение, вероятно, правильное, правильно?
В противном случае, если вы действительно чувствуете себя необычно, и вы не можете беспокоиться о том, чтобы написать funct
два раза, вы можете даже использовать boost::optional
или std::optional
(С++ 14), которые четко выражают намерение аргумента:
void funct(std::optional<double> optional) {
if (optional) {
// we have a double
} else {
// we don't have a double
}
}
И, наконец, у третьего есть три возможных ответа (можете ли вы увидеть шаблон?).
Если вам нужен только конкретный контейнер (зачем вам это нужно, только Бог знает), вы можете просто сделать:
void funct(const std::vector<double>&);
В противном случае вы можете либо использовать шаблоны, как Bartek, объясненные ниже, либо использовать мое любимое решение: итераторы (который также является выбором стандартных алгоритмов, чтобы сделать его менее официальным).
И угадай что? Он также работает с массивами C-стиля (которых вы не должны использовать, кстати). Здесь решение:
template<class Iterator>
void funct(Iterator begin, Iterator end) {
for (auto it = begin; it != end; ++it) {
// do something to element (*it)
}
}
И БУМ. Вы можете использовать его следующим образом:
double x[100];
funct(std::begin(x), std::end(x));
или:
std::vector<double> x(100);
funct(x.begin(), x.end());
Счастливое кодирование.
std::vector
имеет data
функции-члена. Поэтому вы можете использовать его следующим образом:
func(nyVec.data());
Я не нашел в Стандарте, что если вектор пуст, тогда data
должны возвращать 0
. Возможно, это стандартный дефект. Хотя пустой вектор может иметь ненулевую емкость.
Если вам нужно проверить, пустой ли vector
, вы можете написать:
func(myVec.empty() ? NULL : nyVec.data());
Обычно, если вы передаете массив по значению, вы должны указать второй параметр, который будет содержать размер массива. Возможно, было бы лучше, если бы func
был объявлен как:
func(double *, std::vector<double>::size_type);
В этом случае вы можете вызвать функцию как:
func(myVec.data(), myVec.size());
Если вам нужно обработать только один элемент, тогда стандартный подход следующий:
if (!myVec.empty()) func(&myVec.front());
.data
не гарантирует возврата нулевого указателя для пустых векторов.
Создайте функцию утилиты. Это скроет нечеткость и предотвратит дублирование кода.
template <class T>
typename T::value_type* first_ptr(T &&container)
{
return container.empty() ? nullptr : &container.front();
}
int main()
{
funct(first_ptr(myVec));
}
Я бы обернул или изменил функцию на более идиоматический необязательный примитив:
void funct(optional<double&> f);
Тогда подумайте о прохождении. Функция должна, если вектор не пуст, передать первый элемент, и ничего в противном случае.
Непосредственно транскрибируется
if (!v.empty()) {
funct(v.front());
} else {
funct(none);
}
Я бы, вероятно, изменил его на регулярную семантику значений; ссылки на элементы из коллекций напрямую весьма опасны.
Конечно, вы можете упаковать его в функцию многократного использования:
template<class Container>
optional<typename Container::value_type&> myFront(Container& cont) {
if (!cont.empty())
return cont.front();
else
return none;
}
funct(myFront(v));
Теперь вам нужен только lift
:)
.
size() == 0
? Также имя переменной действительно заполнитель, поэтому я не думаю, что это имеет большое значение.
v.size()
, не гарантируется как постоянная операция (хотя все реализации делают это в O (1). empty()
- это путь, плюс он более читабелен.
Пытаться
funct(&myVec.at(0));
Это выполняет проверку границ и будет std::out_of_range
если элемент не находится в пределах контейнера.
&myVec.at(0)
.
Функция std :: vector data()
возвращает указатель на свои внутренние данные (массив), поэтому мы можем использовать ее следующим образом:
if ( !(myVec.size() == 0)) func( myVec.data());
или:
if ( !myVec.empty()) func( myVec.data());
Выбор size()
или empty()
зависит от реализации этих функций, которые вы используете. Стандарт C++ гарантирует, что empty()
является постоянным временем для всех стандартных контейнеров.
size() == 0
предпочтительнее empty
?
На самом деле это не ответ, но в любом случае: это похоже на проблему XY.
Я бы сказал, что ни одно из решений, предложенных в OP, не является хорошим подходом, основанным на следующем: нет смысла вызывать функцию с аргументом нулевого указателя, поэтому я бы сказал, что это должно обрабатываться на сайте вызова. Что-то вроде:
if(!myVec.empty()) funct(&myVec[0]);
else ...
funct
?