ініціалізувати масив const в ініціалізаторі класу в C ++


77

У мене є такий клас на C ++:

class a {
    const int b[2];
    // other stuff follows

    // and here's the constructor
    a(void);
}

Питання полягає в тому, як мені ініціалізувати b у списку ініціалізації, враховуючи те, що я не можу ініціалізувати його всередині тіла функції конструктора, оскільки b є const?

Це не працює:

a::a(void) : 
    b([2,3])
{
     // other initialization stuff
}

Редагувати: Можливий випадок, коли я можу мати різні значення для bрізних екземплярів, але, як відомо, значення є постійними протягом усього життя екземпляра.

Відповіді:


35

Як і інші, ISO C ++ цього не підтримує. Але ви можете обійти це. Просто використовуйте std :: vector замість цього.

int* a = new int[N];
// fill a

class C {
  const std::vector<int> v;
public:
  C():v(a, a+N) {}
};

12
Проблема в тому, що він використовує вектори, які спричиняють додаткові накладні витрати.
vy32

1
Проблема не в тому, що він використовує вектори або один вид пам’яті проти іншого. Це те, що ви не можете безпосередньо ініціалізувати вектор довільним набором значень. Техніка @ CharlesB працюватиме з boost або std, щоб зробити це у два етапи.
Rick Berge

1
Ви завжди можете використовувати a, std::arrayщоб уникнути деяких накладних витрат.
bremen_matt

81

У C ++ 11 відповідь на це запитання тепер змінилася, і ви насправді можете зробити:

struct a {
    const int b[2];
    // other bits follow

    // and here's the constructor
    a();
};

a::a() :
    b{2,3}
{
     // other constructor work
}

int main() {
 a a;
}

25

Це неможливо за чинним стандартом. Я вірю, що ви зможете зробити це в C ++ 0x, використовуючи списки ініціалізаторів (див . Короткий огляд C ++ 0x , автор Bjarne Stroustrup, для отримання додаткової інформації про списки ініціалізаторів та інших приємних функцій C ++ 0x).


12

std::vectorвикористовує купу. Господи, що це було б марнотратством лише для constперевірки стану розуму. Сенс - std::vectorце динамічне зростання під час виконання, а не будь-яка стара перевірка синтаксису, яку слід робити під час компіляції. Якщо ви не збираєтеся рости, створіть клас, щоб обернути звичайний масив.

#include <stdio.h>


template <class Type, size_t MaxLength>
class ConstFixedSizeArrayFiller {
private:
    size_t length;

public:
    ConstFixedSizeArrayFiller() : length(0) {
    }

    virtual ~ConstFixedSizeArrayFiller() {
    }

    virtual void Fill(Type *array) = 0;

protected:
    void add_element(Type *array, const Type & element)
    {
        if(length >= MaxLength) {
            // todo: throw more appropriate out-of-bounds exception
            throw 0;
        }
        array[length] = element;
        length++;
    }
};


template <class Type, size_t Length>
class ConstFixedSizeArray {
private:
    Type array[Length];

public:
    explicit ConstFixedSizeArray(
        ConstFixedSizeArrayFiller<Type, Length> & filler
    ) {
        filler.Fill(array);
    }

    const Type *Array() const {
        return array;
    }

    size_t ArrayLength() const {
        return Length;
    }
};


class a {
private:
    class b_filler : public ConstFixedSizeArrayFiller<int, 2> {
    public:
        virtual ~b_filler() {
        }

        virtual void Fill(int *array) {
            add_element(array, 87);
            add_element(array, 96);
        }
    };

    const ConstFixedSizeArray<int, 2> b;

public:
    a(void) : b(b_filler()) {
    }

    void print_items() {
        size_t i;
        for(i = 0; i < b.ArrayLength(); i++)
        {
            printf("%d\n", b.Array()[i]);
        }
    }
};


int main()
{
    a x;
    x.print_items();
    return 0;
}

ConstFixedSizeArrayFiller і ConstFixedSizeArray є багаторазовими.

Перший дозволяє перевіряти межі часу виконання під час ініціалізації масиву (так само, як і вектор), який згодом може стати const після цієї ініціалізації.

Другий дозволяє розподілити масив усередині іншого об’єкта, який може знаходитись у купі або просто у стеку, якщо там знаходиться об’єкт. Ви не витрачаєте часу на виділення з купи. Він також виконує перевірку const під час компіляції масиву.

b_fillerце крихітний приватний клас для надання значень ініціалізації. Розмір масиву перевіряється під час компіляції за допомогою аргументів шаблону, тому немає шансів вийти за межі.

Я впевнений, що існують більш екзотичні способи змінити це. Це початковий удар. Я думаю, ви можете майже компенсувати будь-який недолік компілятора класами.


3
Яке значення має те, що це на купі? Пам'ять буде використовуватися протягом усього життя об'єкта, незалежно від того, знаходиться він у купі або в стеці. Враховуючи, що багато архітектур мають купу і стек на протилежних сторонах одного і того ж шматка пам'яті, щоб вони могли теоретично зустрітися посередині, чому має значення, де знаходиться об'єкт?
Натан Феллман,

2
@Nathan Fellman: Це можна розглядати як надмірну оптимізацію, але в деяких випадках ви хочете, щоб ваш об'єкт виконував нульове розподіл (для використання в стеку). У такому випадку a new- це занадто багато, і навіть більше, якщо ви знаєте під час компіляції, скільки вам потрібно. Наприклад, деякі реалізації std :: vector розподіляють свої елементи у внутрішньому буфері, замість того, щоб використовувати new, роблячи малі вектори досить дешевими для побудови / знищення.
paercebal

Іноді компілятори достатньо оптимізують, що std::vectorі масиви дають точно такий самий код. Господи.
Себастьян Мах,

9

Стандарт ISO C ++ не дозволяє вам цього робити. Якби це сталося, синтаксис мабуть був би:

a::a(void) :
b({2,3})
{
    // other initialization stuff
}

Або щось подібне. З вашого запитання насправді звучить так, що ви хочете - це постійний клас (він же статичний), який є масивом. C ++ дозволяє вам це робити. Подобається так:

#include <iostream>

class A 
{
public:
    A();
    static const int a[2];
};

const int A::a[2] = {0, 1};

A::A()
{
}

int main (int argc, char * const argv[]) 
{
    std::cout << "A::a => " << A::a[0] << ", " << A::a[1] << "\n";
    return 0;
}

Результатом є:

A::a => 0, 1

Тепер, звичайно, оскільки це статичний член класу, він однаковий для кожного екземпляра класу A. Якщо це не те, що ви хочете, тобто ви хочете, щоб кожен екземпляр A мав різні значення елементів у масиві a, то ви робите помилка спроби зробити для початку масив const. Ви просто повинні робити це:

#include <iostream>

class A 
{
public:
    A();
    int a[2];
};

A::A()
{
    a[0] = 9; // or some calculation
    a[1] = 10; // or some calculation
}

int main (int argc, char * const argv[]) 
{
    A v;
    std::cout << "v.a => " << v.a[0] << ", " << v.a[1] << "\n";
    return 0;
}

1
чому помилкою є зробити масив const для початку? Що робити, якщо я хочу, щоб значення залишалися однаковими протягом життя екземпляра, як якийсь ідентифікатор?
Натан Феллман,

Тоді ви повинні використовувати тип переліку.
ордж

1
як мені тут використовувати тип переліку?
Натан Феллман,

4

Там, де я маю постійний масив, це завжди робилося як статичне. Якщо ви можете прийняти це, цей код слід скомпілювати та запустити.

#include <stdio.h>
#include <stdlib.h>

class a {
        static const int b[2];
public:
        a(void) {
                for(int i = 0; i < 2; i++) {
                        printf("b[%d] = [%d]\n", i, b[i]);
                }
        }
};

const int a::b[2] = { 4, 2 };

int main(int argc, char **argv)
{
        a foo;
        return 0;
}

1
це передбачає, що я справді хочу статичний член, але це не завжди так. Насправді я міг би мати масив const, який має різні значення для різних екземплярів класу, але значення ніколи не змінюються протягом життя класу
Натан Феллман,

ну, якщо ваш конструктор не приймає жодних параметрів, тоді всі екземпляри все одно матимуть однакові значення. Окрім цього, ти маєш рацію.

"Стандарт ISO C ++ не дозволяє" - непогано вказати, яку версію стандарту ISO C ++ ви маєте на увазі
mloskot


3

Рішенням без використання купи з std::vectorє використання boost::array, хоча ви не можете ініціалізувати члени масиву безпосередньо в конструкторі.

#include <boost/array.hpp>

const boost::array<int, 2> aa={ { 2, 3} };

class A {
    const boost::array<int, 2> b;
    A():b(aa){};
};

3

Як щодо емуляції масиву const за допомогою функції доступу? Він нестатичний (як ви просили), і для нього не потрібні stl або будь-яка інша бібліотека:

class a {
    int privateB[2];
public:
    a(int b0,b1) { privateB[0]=b0; privateB[1]=b1; }
    int b(const int idx) { return privateB[idx]; }
}

Оскільки a :: privateB є приватним, він фактично є постійним поза a ::, і ви можете отримати до нього доступ, подібний до масиву, наприклад

a aobj(2,3);    // initialize "constant array" b[]
n = aobj.b(1);  // read b[1] (write impossible from here)

Якщо ви бажаєте використовувати пару класів, ви можете додатково захистити privateB від функцій-членів. Це можна зробити, успадкувавши a; але я думаю, що віддаю перевагу допису Джона Гаррісона comp.lang.c ++ із використанням класу const.


Це цікавий підхід! Дякую!
Nathan Fellman

2

цікаво, що в C # у вас є ключове слово const, що перекладається на статичний const C ++, на відміну від readonly, який може бути встановлений лише в конструкторах та ініціалізаціях, навіть нестандартними, наприклад:

readonly DateTime a = DateTime.Now;

Я згоден, якщо у вас є попередньо визначений масив const, ви можете зробити його статичним. На той момент ви можете використовувати цей цікавий синтаксис:

//in header file
class a{
    static const int SIZE;
    static const char array[][10];
};
//in cpp file:
const int a::SIZE = 5;
const char array[SIZE][10] = {"hello", "cruel","world","goodbye", "!"};

однак я не знайшов способу обійти постійну "10". Причина ясна, однак, вона потрібна, щоб знати, як здійснити доступ до масиву. Можлива альтернатива - використовувати #define, але мені не подобається цей метод, і я #undef в кінці заголовка, з коментарем для редагування там на CPP, а також у разі зміни.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.