Это плохая практика для шаблонов размеров массивов при вызове методов, которые принимают в массивах? - PullRequest
0 голосов
/ 20 февраля 2020

Я пишу реализацию для нейронной сети, и я передаю количество узлов в каждом слое в конструктор. Вот мой конструктор:

class Network {
   public:
      template<size_t n>
      Network(int inputNodes, int (&hiddenNodes)[n], int outputNodes);
};

Мне интересно, если это плохая практика использовать шаблоны для указания размера массива. Должен ли я делать что-то подобное вместо этого?

class Network {
   public:
      Network(int inputNodes, int numHiddenLayers, int* hiddenNodes, int outputNodes);
};

Ответы [ 3 ]

1 голос
/ 20 февраля 2020

Используйте std::span<int> или напишите свой собственный.

struct int_span {
  int* b = 0;
  int* e = 0;

  // iteration:
  int* begin() const { return b; }
  int* end() const { return e; }

  // container-like access:
  int& operator[](std::size_t i) const { return begin()[i]; }
  std::size_t size() const { return end()-begin(); }
  int* data() const { return begin(); }

  // implicit constructors from various contiguous buffers:
  template<std::size_t N>
  int_span( int(&arr)[N] ):int_span( arr, N ) {}
  template<std::size_t N>
  int_span( std::array<int, N>& arr ):int_span( arr.data(), N ) {}
  template<class A>
  int_span( std::vector<int, A>& v ):int_span(v.data(), v.size()) {}

  // From a pair of pointers, or pointer+length:
  int_span( int* s, int* f ):b(s),e(f) {}
  int_span( int* s, std::size_t len ):int_span(s, s+len) {}

  // special member functions.  Copy is enough:
  int_span() = default;

  // This is a view type; so assignment and copy is copying the selection,
  // not the contents:
  int_span(int_span const&) = default;
  int_span& operator=(int_span const&) = default;
};

там мы идем; int_span с представляет представление непрерывного буфера int с некоторого размера.

class Network {
  public:
    Network(int inputNodes, int_span hiddenNodes, int outputNodes);
};
1 голос
/ 20 февраля 2020

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

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

size_t n;
std::cin >> n;
Network<n> network(...); // compile error

Третья проблема с шаблонным подходом заключается в том, что компилятору придется создавать специализацию функции для каждого возможного размера, который вы используете. Для малых значений n это может дать некоторые преимущества, поскольку компилятор может оптимизировать каждую специализацию лучше, когда ему известно точное значение (например, путем развертывания циклов), но для больших значений он, вероятно, не сможет оптимизировать его лучше, чем если бы он не знал размер. А наличие нескольких специализаций может означать, что кэш инструкций в вашем процессоре легче удаляется, что двоичный файл вашей программы больше и, следовательно, использует больше дискового пространства и памяти.

Так что, вероятно, гораздо лучше передать размер как переменная, или вместо использования размера и указателя на массив, используйте (ссылку на) контейнер STL, или, если вы можете использовать C ++ 20, рассмотрите возможность использования std::span.

0 голосов
/ 21 февраля 2020

Исходя из того, как вы пишете второй аргумент функции

int (&hiddenNodes)[n]

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

Итак, забудьте о шаблонах. Go std::vector<int>.

Единственное преимущество использования шаблона (или std::array) здесь заключается в том, что компилятор может оптимизировать ваш код лучше, чем с std::vector. Однако шансы на то, что вы сможете его использовать, очень малы, и даже если вам это удастся, ускорение вряд ли будет измеримо.

Преимущество std::vector заключается в том, что он практически такой же быстрый и простой в использовании, как и std::array, но гораздо более гибкий (его размер настраивается во время выполнения). Если вы go std::array или шаблоны и собираетесь использовать в своей программе скрытые слои разных размеров, вскоре вам придется превратить другие части вашей программы в шаблоны, и скорее всего, вместо реализации нейронной сети вы будете бороться с шаблонами. Это того не стоит.

Однако, когда у вас будет работающая реализация вашего NN, основанная на std::vector, вы сможете ТОГДА рассмотреть его оптимизацию, которая может включать std::array или шаблоны. Но я на 99,999% уверен, что вы останетесь с std::vector.

Я никогда не реализовывал нейронную сеть, но провел много трудоемких симуляций. Первый выбор всегда std::vector, и только если у кого-то есть специальные, четко определенные требования к контейнеру данных, он использует другие контейнеры.

Наконец, имейте в виду, что std::array выделяется стеком, тогда как std::vector выделяется в куче. Куча намного больше, и в некоторых случаях ios это важный фактор для рассмотрения.

EDIT

Вкратце:

  • , если размер массива может изменяться свободно, никогда не передавайте его значение в качестве параметра шаблона. Используйте std::vector
  • Если он может принимать 2, 3, 4, возможно, 5 размеров из фиксированного набора, вы МОЖЕТЕ рассмотреть std::array, но std::vector, скорее всего, будет столь же эффективным, и код будет быть проще
  • Если массив всегда будет иметь одинаковый размер, известный во время компиляции, и ограниченный размер стека функций не является проблемой, используйте std::array.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...