Передача указателя на первый элемент вектора, если есть

0

У меня есть функция, использующая 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());

любое предложение?

Какой стандартный подход?

  • 0
    vOv С таким же успехом я могу сказать, почему вы не можете переписать функцию, чтобы принять разумный тип?
  • 0
    Что вы хотите сделать внутри funct ?
Показать ещё 1 комментарий
Теги:
pointers
iterator
vector

7 ответов

3
Лучший ответ

Ну, стандартный тип типа std::vector имеет функцию-член, называемую data, которая должна возвращать указатель на базовое хранилище. По-видимому, data() - это не более &front() с гарантией того, что:

Указатель таков, что диапазон [data(); data() + size()) [data(); data() + size()) всегда является допустимым диапазоном, даже если контейнер пуст.

Поэтому я бы сказал, что оба:

funct(vector.data());
funct(&vector.front());

можно безопасно использовать.

Но реальный вопрос: что вы пытаетесь сделать внутри функции?

Я вижу 3 очевидных ответа на это, и я собираюсь предложить альтернативы для всех:

  1. Мне нужен только один элемент массива
  2. Мне нужен дополнительный аргумент
  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());

Счастливое кодирование.

  • 0
    funct (std :: begin (x), std :: end (x));
2

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());
  • 4
    .data не гарантирует возврата нулевого указателя для пустых векторов.
  • 1
    как это более безопасно, чем функция OP (myVec.empty ()? NULL: & myVec [0]);
Показать ещё 8 комментариев
1

Создайте функцию утилиты. Это скроет нечеткость и предотвратит дублирование кода.

template <class T>
typename T::value_type* first_ptr(T &&container)
{
  return container.empty() ? nullptr : &container.front();
}


int main()
{
  funct(first_ptr(myVec));
}
1

Я бы обернул или изменил функцию на более идиоматический необязательный примитив:

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 :).

  • 0
    @ piotruś Почему лучше использовать size() == 0 ? Также имя переменной действительно заполнитель, поэтому я не думаю, что это имеет большое значение.
  • 0
    @ piotruś, фактически использующее v.size() , не гарантируется как постоянная операция (хотя все реализации делают это в O (1). empty() - это путь, плюс он более читабелен.
Показать ещё 9 комментариев
1

Пытаться

funct(&myVec.at(0));

Это выполняет проверку границ и будет std::out_of_range если элемент не находится в пределах контейнера.

  • 0
    Это должно быть &myVec.at(0) .
  • 0
    @larsmans Да, конечно, хороший улов.
Показать ещё 1 комментарий
1

Функция std :: vector data() возвращает указатель на свои внутренние данные (массив), поэтому мы можем использовать ее следующим образом:

if ( !(myVec.size() == 0)) func( myVec.data());

или:

if ( !myVec.empty()) func( myVec.data());

Выбор size() или empty() зависит от реализации этих функций, которые вы используете. Стандарт C++ гарантирует, что empty() является постоянным временем для всех стандартных контейнеров.

  • 1
    Теперь, может быть, вы ответите здесь; почему size() == 0 предпочтительнее empty ?
  • 0
    @BartekBanachewicz - это твой собственный блог, который ты везде пишешь свои внутренние мысли? Я ответил на ваш вопрос там, где на него нужно ответить
Показать ещё 2 комментария
0

На самом деле это не ответ, но в любом случае: это похоже на проблему XY.

Я бы сказал, что ни одно из решений, предложенных в OP, не является хорошим подходом, основанным на следующем: нет смысла вызывать функцию с аргументом нулевого указателя, поэтому я бы сказал, что это должно обрабатываться на сайте вызова. Что-то вроде:

if(!myVec.empty()) funct(&myVec[0]);
else ...

Ещё вопросы

Сообщество Overcoder
Наверх
Меню