【发布时间】:2014-10-29 09:26:21
【问题描述】:
我想知道以下代码是否不被 C++ 标准接受。
int n{ 10 };
double* p = new double[0];
double* q = p + n;
std::cout << "n = " << static_cast<int>(q - p) << std::endl;
我希望该程序显示 n 的值。
由于这个问题可能看起来很奇怪,这里是对这个问题的起源的解释。我想在 2D 中设计一个动态数组类(想想 std::vector 类型的容器,但在 2D 中而不是 1D 中)。一个简单的方法是:
template <typename T>
class Array2D<T> {
private:
T* data_;
int nb_rows_;
int nb_columns_;
public:
...
};
不幸的是,这种设计不像循环这样的 SIMD 友好
Array2D<int> A(5, 6);
for (int i = 0; i < A.nb_rows(); ++i) {
for (int j = 0; j < A.nb_columns(); ++j) {
A(i, j) += 1;
}
}
将无法矢量化,因为编译器无法确定 nb_columns_ 在循环期间是否因指针别名而未更改。因此,我使用与大多数 std::vector 实现相同的设计,其中向量的大小“隐藏”在指针中。
template <typename T>
class Array2D<T> {
private:
T* data_;
T* nb_rows_;
T* nb_columns_;
public:
Array2D(int n, int p) {
data_ = new T[n * p];
nb_rows_ = data_ + n;
nb_columns_ = data_ + p;
}
...
int nb_columns() const {
return static_cast<int>(nb_columns_ - data_);
}
...
};
只要 n >= 1 和 p >= 1,这个设计就可以很好地工作。但是如果 n = 0 和 p = 5,你最终会遇到上面解释的那种“问题”。由于我的类中有以下方法,构造一个 0 行的 Array2D 可能很有用
void push_back(const Array1D<T>& B);
接受大小为 p 的 Array1D(使用断言检查)并向我的 Array2D 对象添加一行。你可以这样做:
Array2D<double> A(0, 10);
Array1D<double> B(10);
// work with B
A.push_back(B);
代码在 clang、g++ 和 icpc 上运行良好,但我仍然想知道它是否有效。 C++11 标准的第 5.7 节是关于这个问题的,但谈论的是“数组对象”。我想知道我的 p 是否指向他们所谓的“数组对象”,或者数组对象是否是诸如“double p[5]”之类的东西。
【问题讨论】:
-
@Joachim Pileborg:我想确定这一点。你能在这一点上引用标准吗?也可能有一些指针溢出,因为 data_ + n 可能会溢出。
-
我不知道如何处理零大小的数组?
-
正如 Ali Kazmi 所说,零大小的数组有什么好处?难道你不能只检查 n*p==0 然后像这样返回:
Array2D(int n, int p) { if (n*p==0) return; data_ = new T[n * p]; nb_rows_ = data_ + n; nb_columns_ = data_ + p; } -
@Ali Kazmi: new double[0] 被 C++ 标准接受。它可能有用的原因是我的类被设计成可以添加一行(如 std::vector 中的 push_back)。
-
@JoachimPileborg 指针算法仅针对同一数组内的指针定义,或指向该数组末尾的指针。在他的示例中,将 10 添加到
p会导致指针不再满足这些条件,因此是未定义的行为。
标签: c++ arrays language-lawyer