ініціалізувати масив 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.