Нетиповые параметры шаблона и выделение памяти массива

В книге, которую я читаю, есть пример класса, который используется для объяснения концепций:

class Gameboard{
public:
    Gameboard(int inWidth, int inHeight);
    Gameboard(const Spreadsheet& src);
    Gameboard& operator=(const Spreadsheet& rhs);

private:
    GamePiece** mCells;
    size_t width;
    size_t height;
};

затем они вводят шаблоны и вводят обновленный класс:

template<typename T>
class Grid{
public:
    Grid<T>(int inWidth, int inHeight);
    Grid<T>(const T& src);
    Grid<T>& operator=(const T& rhs);

private:
    T** mCells;
    size_t width;
    size_t height;
};

наконец, они вводят нетиповые параметры шаблона и говорят, что теперь вы можете сделать это:

template<typename T, size_t WIDTH, size_t HEIGHT>
class Grid{
public:
    Grid<T>();
    Grid<T>(const T& src);
    Grid<T>& operator=(const T& rhs);

private:
    T mCells[WIDTH][HEIGHT];
};

Из книги:

В классе шаблона Grid вы можете использовать нетиповые параметры шаблона, чтобы указать высоту и ширину сетки вместо их указания в конструкторе. Принципиальное преимущество указания нетиповых параметров в списке шаблонов вместо конструктора состоит в том, что значения известны до компиляции кода. Напомним, что компилятор генерирует код для шаблонных методов, подставляя параметры шаблона перед компиляцией. Таким образом, вы можете использовать в своей реализации обычный двумерный массив вместо его динамического выделения.

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

Во-вторых, есть ли какое-то правило C++ (о котором я забываю), которое запрещает объявлять многомерный массив в стеке, отсюда и волнение с этим подходом?

Я пытаюсь понять, в чем преимущество использования нетиповых параметров шаблона в их примере.


person user997112    schedule 29.06.2014    source источник
comment
посмотрите на это как на иллюстрацию, продолжайте учиться, посмотрите на другие источники информации. Я не могу судить о книге только по этому образцу, так что для меня это не очень убедительно, есть много дизайнерских решений, которые можно было бы придать среде, в которой вы будете использовать такой тип, std::array, например, идет по тому же маршруту, предлагая альтернативу std::vector.   -  person pepper_chico    schedule 29.06.2014


Ответы (3)


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

Как видите, ваш массив является непосредственным членом, а не косвенным указателем.

T mCells[WIDTH][HEIGHT];

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

Я не понимаю, почему вы не хотите динамически выделять память в куче?

Вы могли. Но наличие нового массива и медленнее, и более подвержено ошибкам (т.е. его следует удалить и т. д.).

Во-вторых, есть ли какое-то правило C++ (о котором я забываю), которое запрещает объявлять многомерный массив в стеке, отсюда и ажиотаж с этим подходом?

Нет. Но размер любого массива должен быть константой времени компиляции. Нетиповые параметры шаблона как раз и являются константами времени компиляции. Если бы они были просто параметрами функции типа int, компилятор не знал бы их значения, и создание массива (многомерного или иного) в стеке было бы незаконным.

person Armen Tsirunyan    schedule 29.06.2014
comment
'Но размер любого массива должен быть константой времени компиляции' По крайней мере, у GCC есть расширения (для массивов, выделенных в локальном стеке), которые должны решить эту проблему? - person πάντα ῥεῖ; 29.06.2014

«Во-первых, означает ли это, что многомерный массив будет в стеке»

Если Gameboard находится в стеке (т.е. это локальная переменная), то да, массив тоже находится в стеке.

«Я не понимаю, почему вы не хотите динамически выделять память в куче?»

Для скорости. В этом случае, поскольку Gameboard, вероятно, останется надолго, в этом нет необходимости. На самом деле, std::vector<std::vector<GamePiece>> mCells было бы лучше, чем ручные массивы.

"есть ли какое-то правило С++ (о котором я забыл), которое запрещает объявлять многомерный массив в стеке"

Нет, это допустимо. Но, как и в случае с обычными массивами, размеры должны быть известны во время компиляции.

«Я пытаюсь понять, в чем преимущество использования нетиповых параметров шаблона в их примере».

Это надуманный пример.

Допустим, вы хотите создать целочисленный класс произвольной точности. Чем выше точность, тем больше места требуется. Этот класс можно часто создавать и уничтожать, как обычную целочисленную переменную. Если данные распределяются динамически, это происходит медленнее. Указав требуемую точность с помощью параметра шаблона, можно создать обычный массив требуемого размера как член класса. Всякий раз, когда класс помещается в стек, то же самое будет и с массивом, и это будет быстрее.

person Neil Kirk    schedule 29.06.2014

Не ссылаясь на конкретные образцы и источники, которые вы даете:

Я не испытываю восторга от этого подхода в отношении динамического распределения памяти.

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

Я не понимаю, почему вы не хотите динамически выделять память в куче?

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

Кроме того, помимо вышеперечисленных причин, динамическое размещение приводит к снижению производительности во время выполнения, что нежелательно для приложений, критически важных для производительности (например, механизмов рендеринга игровых фреймворков).

Вкратце и в общих чертах:
Если у вас есть что-то, что можно настроить во время компиляции, отдайте предпочтение этому, а не настройке во время выполнения.

person πάντα ῥεῖ    schedule 29.06.2014