Инициализация константного массива const в шаблонном классе C++

Я хотел создать template <typename T> class InitConst, некоторые элементы которого являются массивами T. Я хотел заполнить эти массивы во время инициализации объектов класса, а затем быть уверенным, что эти массивы больше не изменятся, поэтому я определил член const T* const v для каждого такого массива (я знаю, что первый const относится к элементам, а второй к указатели).

Поэкспериментировав с этим, я пришел к выводу, что определение "v" как "константный указатель на const" вынудило меня выделить и заполнить соответствующий массив, прежде чем окончательно инициализировать "v" адресом этого массива. Более того, поскольку я не могу инициализировать "v" внутри тела конструктора класса "InitConst", я пришел к выводу, что мне нужна вспомогательная функция, роль которой заключалась в том, чтобы (а) получить аргументы конструктора, (б) выделить и заполнить массивы и (c) использовать некоторые статические указатели для хранения адресов этих массивов. Эти статические указатели, наконец, будут использоваться конструктором для инициализации константных указателей, поэтому я получил такой код:

template <typename T>
class InitConst
  {
  public:
  const T* const v1;
  const T* const v2;
  const T* const v3;

  InitConst(T a1, T a2, T a3, int n)
    : v1( init(a1,a2,a3,n) ), v2(init_p.temp_v2), v3(init_p.temp_v3)
    { }

  private:
  struct Tinit {
    T* temp_v1;
    T* temp_v2;
    T* temp_v3;
    };
  static Tinit init_p;

  T* init (T a1, T a2, T a3, int n)
    {
    init_p.temp_v1 = new T[n];
    init_p.temp_v2 = new T[n];
    init_p.temp_v3 = new T[n];
    // populate "temp_v1", "temp_v2" and "temp_v3" using the method's arguments.
    return init_p.temp_v1;
    } // End method init.
  }; // End class InitConst.

template <typename T>
typename InitConst<T>::Tinit InitConst<T>::init_p;

Этот код компилируется и работает, как и ожидалось, но я считаю этот дизайн искаженным: я привык к простому коду, где я сначала выделяю массив, затем вычисляю его элементы (оба действия обычно происходят в теле конструктора), а затем использую массив. Выше, напротив, конструктор почти ничего не делает сам по себе: его роль передается методу «init», который фактически создает массивы и использует некоторые вспомогательные указатели, чтобы передать их обратно конструктору.

Я тогда удивляюсь:

a) Необходим ли этот дизайн (или что-то подобное), если я решу объявить каждый указатель как «константный указатель на константу», или есть более чистый способ сделать это?

б) Объявление каждого указателя как «константный указатель на константу» было способом защиты от неправильного использования класса, но, возможно, мне это не нужно. Несколько менее строгий подход состоял бы в том, чтобы объявить "v1" и его братьев и сестер как частные члены, чтобы их нельзя было изменить вне класса. Однако это также предотвратит их чтение из-за пределов класса, и я не хочу иметь метод «read_vx» для каждого массива «vx». Что я тогда мог с этим поделать, то есть какой подход привел бы к более читаемому коду и при этом гарантировал бы как минимум невозможность изменения массивов извне класса?

Заранее спасибо, и извините за длинную прозу.

Редактировать: как я прокомментировал ниже, в моем реальном коде различные массивы, которые я хочу вычислить, гораздо эффективнее вычисляются вместе, и именно поэтому я использую одну функцию «инициализации». Аргументы для «init» («a1», «a2», «a3»), которые я предоставил в примере, на самом деле вводили в заблуждение.


person Pablo M. S. Farias    schedule 26.05.2013    source источник
comment
Это, вероятно, лучше подходит для CodeReview   -  person Andy Prowl    schedule 26.05.2013
comment
Для меня лучший дизайн - это избегать голых указателей и использовать типы STL.   -  person masoud    schedule 26.05.2013
comment
О, спасибо: я не знал о CodeReview, но я собираюсь использовать его в будущем для подобных вопросов.   -  person Pablo M. S. Farias    schedule 26.05.2013
comment
Этот вопрос кажется не по теме, потому что он касается обзора дизайна программного обеспечения.   -  person nijansen    schedule 19.09.2013


Ответы (2)


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

template <typename T>
class InitConst
{
public:
    const T* const v1;
    const T* const v2;
    const T* const v3;

    InitConst(T a1, T a2, T a3, int n)
        : v1( new T[n] ), v2( new T[n] ), v3( new T[n] )
    {
        T* t1 = const_cast<T*>(v1);
        T* t2 = const_cast<T*>(v2);
        T* t3 = const_cast<T*>(v3);

        //  do intialization here
    }
};



[ПРИМЕЧАНИЕ. Следующее решение неприменимо из-за комментариев OP]

Было бы лучше, если бы init помогала инициализировать одну переменную-член за раз, а не все три. Это значительно уменьшит код и устранит необходимость в переменной static, которую нужно будет определить для каждого типа T, используемого с InitConst. Вы также можете изменить init на static, чтобы предотвратить случайное использование переменных-членов, которые не были инициализированы.

template <typename T>
class InitConst
{
public:
    const T* const v1;
    const T* const v2;
    const T* const v3;

    InitConst(T a1, T a2, T a3, int n)
        : v1( init(a1,n) ), v2( init(a2,n)), v3(init(a3,n))
    { }

private:
    static const T* init (T a, int n)
    {
        T* v = new T[n];
        // populate "v" from "a"
        return v
    }
};
person Captain Obvlious    schedule 26.05.2013
comment
Во-первых, спасибо за ответ. Теперь, собственно, мой реальный код полон зависимостей (раньше я об этом не упоминал): хотя я мог бы вычислять все массивы по отдельности, гораздо эффективнее они вычисляются все вместе, поэтому я вычисляю их все в одном методе. Но совет о статике и инициализации применим, еще раз спасибо! - person Pablo M. S. Farias; 26.05.2013
comment
Если вы можете обновить свой вопрос, включив в него информацию о зависимостях, есть вероятность, что может быть предоставлено полезное решение. Просто сделайте дополнение с изложением deps. - person Captain Obvlious; 26.05.2013
comment
Большое спасибо, @captain-obvlious! const_cast очень мощен и достаточен даже в случае других требований моего реального кода, которые я не упомянул в примере. На самом деле, оно настолько мощное, что теперь я понимаю, что ключевое слово const не такое мощное, как я думал. - person Pablo M. S. Farias; 28.05.2013
comment
С великой силой приходит... ну, вы поняли. Просто убедитесь, что вы используете его с умом ;) - person Captain Obvlious; 28.05.2013

Нет необходимости в struct Tinit:

tempalate<typename T>
InitConst::InitConst(T a1, T a2, T a3, int n)
  : v1(init(a1,n)), v2(init(a2,n)), v3(init(a3,n))
{}

tempalate<typename T>
T* InitConst::init (T a, int n)
{
  T* result = new T[n];
  // TODO: populate result using a
  return result
};

// TODO implement destructor, copy constuctor and
// copy assignement operator
person Oswald    schedule 26.05.2013