Initialisieren Sie ein const-Array in einem Klasseninitialisierer in C ++

76

Ich habe die folgende Klasse in C ++:

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

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

Die Frage ist, wie ich b in der Initialisierungsliste initialisiere, da ich es nicht im Hauptteil der Funktion des Konstruktors initialisieren kann, weil b ist const.

Das funktioniert nicht:

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

Bearbeiten: Der Fall ist, wenn ich unterschiedliche Werte für bunterschiedliche Instanzen haben kann, die Werte jedoch bekanntermaßen für die Lebensdauer der Instanz konstant sind.

Nathan Fellman
quelle

Antworten:

34

Wie die anderen sagten, unterstützt ISO C ++ das nicht. Aber Sie können es umgehen. Verwenden Sie stattdessen einfach std :: vector.

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

class C {
  const std::vector<int> v;
public:
  C():v(a, a+N) {}
};
Weipeng L.
quelle
12
Das Problem dabei ist, dass Vektoren verwendet werden, die zusätzlichen Overhead verursachen.
vy32
1
Das Problem ist nicht, dass es Vektoren oder eine Art von Speicher gegen eine andere verwendet. Es ist so, dass Sie den Vektor nicht direkt mit einem beliebigen Satz von Werten initialisieren können. Die Technik von @ CharlesB arbeitet mit Boost oder Standard, um dies in zwei Schritten zu tun.
Rick Berge
1
Sie können immer a verwenden std::array, um einen Teil dieses Overheads zu vermeiden.
bremen_matt
78

Mit C ++ 11 hat sich die Antwort auf diese Frage geändert und Sie können tatsächlich Folgendes tun:

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;
}
Flexo
quelle
25

Dies ist im aktuellen Standard nicht möglich. Ich glaube, Sie können dies in C ++ 0x mithilfe von Initialisierungslisten tun ( weitere Informationen zu Initialisierungslisten und anderen nützlichen C ++ 0x-Funktionen finden Sie unter Ein kurzer Blick auf C ++ 0x von Bjarne Stroustrup).

Luc Touraille
quelle
12

std::vectorbenutzt den Haufen. Meine Güte, was für eine Verschwendung, die nur für eine constÜberprüfung der geistigen Gesundheit wäre. Der Punkt std::vectorist dynamisches Wachstum zur Laufzeit, keine alte Syntaxprüfung, die zur Kompilierungszeit durchgeführt werden sollte. Wenn Sie nicht wachsen möchten, erstellen Sie eine Klasse, um ein normales Array zu verpacken.

#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;
}

ConstFixedSizeArrayFillerund ConstFixedSizeArraysind wiederverwendbar.

Die erste ermöglicht die Überprüfung der Laufzeitgrenzen während der Initialisierung des Arrays (wie bei einem Vektor), die später constnach dieser Initialisierung erfolgen kann.

Mit der zweiten Option kann das Array innerhalb eines anderen Objekts zugewiesen werden, das sich auf dem Heap oder einfach auf dem Stapel befinden kann, wenn sich dort das Objekt befindet. Es ist keine Zeitverschwendung, sich vom Haufen zu trennen. Es führt auch eine Konstantenprüfung zur Kompilierungszeit für das Array durch.

b_fillerist eine winzige private Klasse, die die Initialisierungswerte bereitstellt. Die Größe des Arrays wird beim Kompilieren mit den Vorlagenargumenten überprüft, sodass keine Gefahr besteht, dass Grenzen überschritten werden.

Ich bin sicher, es gibt exotischere Möglichkeiten, dies zu ändern. Dies ist ein erster Stich. Ich denke, Sie können jeden Mangel des Compilers mit Klassen so gut wie ausgleichen.

Matthew
quelle
3
Was macht es aus, dass es auf dem Haufen ist? Der Speicher wird während der gesamten Lebensdauer des Objekts verwendet, unabhängig davon, ob es sich auf dem Heap oder dem Stapel befindet. In Anbetracht der Tatsache, dass sich bei vielen Architekturen der Heap und der Stack auf gegenüberliegenden Seiten desselben Speicherblocks befinden, sodass sie sich theoretisch in der Mitte treffen können, warum spielt es dann eine Rolle, wo sich das Objekt befindet?
Nathan Fellman
2
@ Nathan Fellman: Dies kann als Überoptimierung angesehen werden. In einigen Fällen möchten Sie jedoch, dass Ihr Objekt eine Nullzuweisung vornimmt (für die Verwendung auf dem Stapel). In diesem Fall ist a newzu viel und noch mehr, wenn Sie zur Kompilierungszeit wissen, wie viel Sie benötigen. Beispielsweise ordnen einige Implementierungen von std :: vector seine Elemente in einem internen Puffer zu, anstatt sie zu verwenden new, wodurch kleine Vektoren zum Erstellen / Zerstören recht billig sind.
Paercebal
Manchmal optimieren Compiler genug, std::vectorund Arrays liefern genau den gleichen Code. Meine Güte.
Sebastian Mach
9

Nach dem ISO-Standard C ++ können Sie dies nicht tun. Wenn dies der Fall wäre, wäre die Syntax wahrscheinlich:

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

Oder etwas in diese Richtung. Aus Ihrer Frage geht hervor, dass das, was Sie wollen, ein konstantes Klassenmitglied (auch bekannt als statisches Mitglied) ist, das das Array ist. Mit C ++ können Sie dies tun. Wie so:

#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;
}

Die Ausgabe ist:

A::a => 0, 1

Da dies ein statisches Klassenmitglied ist, ist es natürlich für jede Instanz der Klasse A gleich. Wenn dies nicht das ist, was Sie wollen, dh Sie möchten, dass jede Instanz von A unterschiedliche Elementwerte im Array a hat, das Sie erstellen der Fehler, zunächst zu versuchen, das Array const zu machen. Sie sollten dies einfach tun:

#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;
}
orj
quelle
1
Warum ist es ein Fehler, das Array zunächst const zu machen? Was ist, wenn ich möchte, dass die Werte für das Leben der Instanz gleich bleiben, wie eine Art ID?
Nathan Fellman
Dann sollten Sie einen Aufzählungstyp verwenden.
Orj
1
Wie würde ich hier einen Aufzählungstyp verwenden?
Nathan Fellman
4

Wo ich ein konstantes Array habe, wurde es immer als statisch ausgeführt. Wenn Sie das akzeptieren können, sollte dieser Code kompiliert und ausgeführt werden.

#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;
}
Daniel Bungert
quelle
1
das setzt voraus, dass ich tatsächlich ein statisches Mitglied will, aber das ist nicht immer der Fall. Ich könnte tatsächlich ein const-Array haben, das unterschiedliche Werte für verschiedene Instanzen der Klasse hat, aber die Werte ändern sich während der Lebensdauer der Klasse nie
Nathan Fellman
Wenn Ihr Konstruktor keine Parameter akzeptiert, haben alle Instanziierungen sowieso die gleichen Werte. Davon abgesehen hast du recht.
"ISO-Standard C ++ lässt Sie nicht" - es ist eine gute Idee, anzugeben, welche Version des ISO-C ++ - Standards Sie im Sinn haben
Mloskot
3

Eine Lösung ohne Verwendung des Heaps mit std::vectorist die Verwendung boost::array, obwohl Sie Array-Mitglieder nicht direkt im Konstruktor initialisieren können.

#include <boost/array.hpp>

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

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

Wie wäre es mit der Emulation eines const-Arrays über eine Accessor-Funktion? Es ist nicht statisch (wie von Ihnen angefordert) und erfordert weder stl noch eine andere Bibliothek:

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

Da a :: privateB privat ist, ist es außerhalb eines :: effektiv konstant, und Sie können ähnlich wie bei einem Array darauf zugreifen, z

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

Wenn Sie bereit sind, zwei Klassen zu verwenden, können Sie privateB zusätzlich vor Mitgliedsfunktionen schützen. Dies könnte durch Erben von a geschehen; aber ich glaube, ich bevorzuge John Harrisons comp.lang.c ++ - Beitrag mit einer const-Klasse.

Pete
quelle
Dies ist ein interessanter Ansatz! Vielen Dank!
Nathan Fellman
2

Interessanterweise haben Sie in C # das Schlüsselwort const, das in die statische Konstante von C ++ übersetzt wird, im Gegensatz zu readonly, das nur bei Konstruktoren und Initialisierungen festgelegt werden kann, selbst durch Nichtkonstanten, z.

readonly DateTime a = DateTime.Now;

Ich bin damit einverstanden, wenn Sie ein vordefiniertes const-Array haben, können Sie es auch statisch machen. An diesem Punkt können Sie diese interessante Syntax verwenden:

//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", "!"};

Ich habe jedoch keinen Weg gefunden, um die Konstante '10' zu umgehen. Der Grund ist jedoch klar, es muss wissen, wie der Zugriff auf das Array durchgeführt wird. Eine mögliche Alternative ist die Verwendung von #define, aber ich mag diese Methode nicht und ich #undef am Ende des Headers, mit einem Kommentar, der dort auch bei CPP bearbeitet werden kann, falls sich etwas ändert.

Nefzen
quelle