在C++中的类初始化器中初始化一个常量数组

83

我在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值,但这些值在实例的生命周期内被认为是常量。

10个回答

84

随着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;
}

请注意,也可以在类定义内部设置值,例如:struct a {const int b[2]{2, 3};};。仍然可以像这个答案所示的那样,在构造函数初始化列表中将数组初始化为不同的值。 - adentinger

34

像其他人所说,ISO C++不支持该功能。但是你可以采用变通方法。只需使用std::vector即可。

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

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

13
问题在于它使用向量会导致额外的开销。 - vy32
1
问题不在于它使用向量,或者一种内存与另一种内存。问题在于你不能直接用任意一组值初始化向量。@CharlesB的技巧可以通过boost或std在两个步骤中完成。 - Rick Berge
1
你可以使用 std::array 来避免一些开销。 - bremen_matt

25

在当前的标准下是不可能的。我相信你可以使用初始化列表在C++0x中实现这一点(有关初始化列表和其他很好的C++0x功能的更多信息,请参见Bjarne Stroustrup的《简要介绍C++0x》)。


14

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

ConstFixedSizeArrayFillerConstFixedSizeArray可重复使用。

第一个允许在初始化数组时进行运行时边界检查(与向量相同),这些数组在初始化之后可以变为const

第二个允许数组在另一个对象内部分配,该对象可以在堆上或仅仅在栈上。没有浪费时间从堆中分配。它还对数组进行编译时常量检查。

b_filler是一个小的私有类,用于提供初始化值。通过模板参数,在编译时检查数组的大小,因此不会越界。

我相信还有更多奇特的方法可以修改它。这是最初的尝试。我认为你可以用类来弥补编译器的任何缺陷。


3
它在堆上有什么关系呢?无论对象是在堆上还是栈上,内存都将在对象的整个生命周期中被使用。考虑到许多架构将堆和栈放在同一块内存的相反位置,以便它们理论上可以在中间相遇,那么对象所在的位置为什么很重要呢? - Nathan Fellman
2
@Nathan Fellman:这可能被视为过度优化,但在某些情况下,您希望对象不进行任何分配(用于堆栈使用)。在这种情况下,new 太多了,即使您在编译时知道需要多少。例如,一些 std::vector 的实现会在内部缓冲区中分配其项,而不是使用 new,使得小向量的构造/销毁非常便宜。 - paercebal
有时候,编译器会进行足够的优化,使得 std::vector 和数组产生完全相同的代码。天哪。 - Sebastian Mach

10

ISO标准的C++不允许你这样做。如果可以的话,语法可能会是:

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是错误的?如果我希望值在实例的生命周期内保持不变,例如某种类型的ID,该怎么办? - Nathan Fellman
那你应该使用枚举类型。 - orj
1
我该如何在这里使用枚举类型? - Nathan Fellman

4

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数组,该数组对类的不同实例具有不同的值,但这些值在类的生命周期内永远不会改变。 - Nathan Fellman
好的,如果你的构造函数不需要参数,那么所有实例化对象都将具有相同的值。除此之外,你是正确的。 - user1115652
ISO标准的C++不允许您这样做 - 最好指定您所考虑的ISO C++标准版本。 - mloskot

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来完成;但我认为我更喜欢John Harrison在comp.lang.c++帖子中使用const类的方法。

这是一个有趣的方法!谢谢! - Nathan Fellman

3
不使用堆内存的解决方案是使用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){};
};

2

有趣的是,在C#中,关键字const相当于C++中的static const,而不是只能在构造函数和初始化时被非常量设置的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中进行编辑更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接