考虑c ++(或c ++ 11),其中我有一些数据,其中2 * N整数代表N对。对于每个偶数i = 0,2,4,6,...,2 * N,它认为(data [i],data [i + 1])形成这样的一对。现在我希望有一种简单的方法来访问这些对,而无需编写如下的循环:
for(int i=0; i<2*N; i+=2) { ... data[i] ... data[i+1] ... }
所以我写了这个:
#include <iostream>
struct Pair {
int first; int second;
};
int main() {
int N=5;
int data[10]= {1,2,4,5,7,8,10,11,13,14};
Pair *pairs = (Pair *)data;
for(int i=0; i<N; ++i)
std::cout << i << ": (" << pairs[i].first << ", " << pairs[i].second << ")" << std::endl;
return 0;
}
输出:
0: (1, 2)
1: (4, 5)
2: (7, 8)
3: (10, 11)
4: (13, 14)
ideaone:http://ideone.com/DyWUA8
正如您所看到的,我将int指针强制转换为Pair指针,这样c ++只会处理我的数据大小为int的两倍。我知道,因为这就是数组的工作方式,数据数组是以两个sizeof(int)对的方式对齐的。但是,我不确定我是否可以假设一个Pair正好是两个sizeof(int)的,并且第一个和第二个成员字段是以该顺序(或对齐)存储的。从技术上讲,在最糟糕的情况下,我可以想象编译器首先存储2个字节,然后存储4个第二个字节,然后存储第一个字节(假设int是4个字节),并以某种方式管理它。当然,这可能是荒谬的,但它是否允许在c ++中?
请注意,我不想将所有数据复制到新阵列并手动将其转换为Pairs。 Imho,这对于语法糖来说是一项昂贵的操作。
我可以假设Pair类对齐吗?结构是否相同?还有其他方法吗?
从我在这里阅读(How is the size of a C++ class determined?),c ++的编译器,而不是语言,是如何在内存中对齐类的。 这是否意味着我注定要复制我的数据或使用讨厌的语法?我可以以某种方式强制c ++语言中的最小对齐,还是需要编译器切换?
答案 0 :(得分:2)
您所做的事情违反了严格的别名规则,因此除了任何可能的大小和对齐问题外,还会导致未定义的行为。
最干净的解决方案是通过在需要时进行一次性转换,以逻辑对而不是平面数据存储数据。不要担心执行数据转换的性能,除非分析显示您正在执行时间。将数据分组成对的清晰度几乎肯定会在长期内得到正确回报。
或者,你可以创建内联函数来抽象地访问名义上的&#34;第一个&#34;和&#34;第二&#34;平面数组数据的属性。
答案 1 :(得分:2)
结构的对齐至少是其成员的最大对齐方式,但它可以更大。此外,编译器可以在您的成员之间添加填充,因此您的代码不安全。
基本上,结构布局的唯一保证是:
您可以使用此定义的第一个保证:
struct Pair {
int p[2];
};
现在,sizeof(Pair)
可能比2*sizeof(int)
更大,但这不应该太重要。
或者,如果你想要额外的乐趣:
typedef int Pair[2];
指向数组的指针很有趣!
无论如何,我的建议是这样做:
int data[10]= {1,2,4,5,7,8,10,11,13,14};
for(int i=0; i<N; ++i)
{
int *pair = data + 2*i;
std::cout << i << ": (" << pairs[0] << ", " << pairs[1] << ")" << std::endl;
}
或者如果您更喜欢额外的乐趣:
typedef int Pair[2];
int data[10]= {1,2,4,5,7,8,10,11,13,14};
Pair *pairs = (Pair*)data;
for(int i=0; i<N; ++i)
{
std::cout << i << ": (" << pairs[i][0] << ", " << pairs[i][1] << ")" << std::endl;
}
答案 2 :(得分:1)
这是否意味着我注定要复制我的数据或使用讨厌的语法?
没有
还有其他方法吗?
是的,使用提供您喜欢的语法的包装类。这是一种方式
#include <iostream>
struct Pairs {
int* _data;
Pairs( int data[] ) : _data(data) {}
int & first( size_t x ) const { return _data[x*2]; }
int & second( size_t x ) const { return _data[x*2+1]; }
};
int main() {
int N=5;
int data[10]= {1,2,4,5,7,8,10,11,13,14};
Pairs pairs( data );
for(int i=0; i<N; ++i)
std::cout << i << ": (" << pairs.first(i) << ", " << pairs.second(i) << ")" << std::endl;
return 0;
}
<强>更新强>
这是一个在结构(如C ++ 11 std :: array)中包装int [2]的解决方案,但在int [2]之后允许编译器填充(实际上是强制)填充。编译器不太可能添加任何额外的填充,但标准并不排除它。我还添加了一个随机访问迭代器,允许将迭代器传递给std :: sort并将原始数据按对进行排序。我为我的一次教育做了这个,可能比它的价值更麻烦。
// http://stackoverflow.com/questions/23480041/is-the-member-field-order-of-a-class-stable
#include <iostream>
#include <algorithm>
#include <stddef.h>
struct Pair {
int _data[2]; // _data[0] and _data[1] are consecutive,
// and _data[0] is at offset 0 (&Pair == &_data[0])
int _unused[6]; // simulate the compiler inserted some padding here
int first() { return _data[0]; }
int second() { return _data[1]; }
int & operator[] ( size_t x ) { return _data[x]; }
friend inline bool operator< ( const Pair & lhs, const Pair & rhs ) {
return lhs._data[0] < rhs._data[0];
}
// it is unlikely that the compiler will add any padding to a struct
// Pair, so sizeof(Pair) == sizeof(_data)
// however, the standard doesn't preclude it, so we define our own
// copy constructor and assignment operator to ensure that nothing
// extraneous is stored
Pair( const Pair& other ) {
_data[0] = other._data[0];
_data[1] = other._data[1];
}
const Pair& operator=( const Pair& other ) {
_data[0] = other._data[0];
_data[1] = other._data[1];
return *this;
}
};
struct Pairs {
int* _data;
size_t _size;
Pairs( int data[], size_t size ) : _data(data), _size(size) {}
Pair & operator[] ( size_t x ) const {
return *reinterpret_cast< Pair * >( _data + 2 * x );
}
class rai
: public std::iterator<std::random_access_iterator_tag, Pair>
{
int * _p;
size_t _size;
size_t _x;
public:
rai() : _p(NULL), _size(0), _x(0) {}
rai( int* data, size_t size )
: _p(data), _size(size), _x(0) {}
friend inline bool operator== (const rai& lhs, const rai& rhs) {
return lhs._p == rhs._p && lhs._x == rhs._x;
}
friend inline bool operator!= (const rai& lhs, const rai& rhs) {
return lhs._p != rhs._p || lhs._x != rhs._x;
}
Pair& operator* () const {
return *reinterpret_cast< Pair * >( _p + 2 * _x );
}
rai& operator+=( ptrdiff_t n ) {
_x += n;
if (_x >= _size) { _x = _size = 0; _p = NULL; }
return *this;
}
rai& operator-=( ptrdiff_t n ) {
if (n > _x) _x = 0;
else _x -= n;
return *this;
}
friend inline rai operator+ ( rai lhs, const ptrdiff_t n ) {
return lhs += n;
}
friend inline rai operator- ( rai lhs, const ptrdiff_t n ) {
return lhs -= n;
}
friend inline bool operator< ( const rai & lhs, const rai & rhs ) {
return *lhs < *rhs;
}
rai& operator++ () { return *this += 1; }
rai& operator-- () { return *this -= 1; }
friend inline ptrdiff_t operator-(const rai& lhs, const rai& rhs) {
return lhs._p == NULL
? (rhs._p == NULL ? 0 : rhs._size - rhs._x)
: lhs._x - rhs._x;
}
};
inline rai begin() { return rai(_data,_size); }
static inline const rai end() { return rai(); }
};
int main() {
int N=5;
int data[10]= {1,2,7,8,13,14,10,11,4,5};
Pairs pairs( data, N );
std::cout << "unsorted" << std::endl;
for(int i=0; i<N; ++i)
std::cout << i << ": (" << pairs[i].first() << ", "
<< pairs[i].second() << ")" << std::endl;
std::sort(pairs.begin(), pairs.end());
std::cout << "sorted" << std::endl;
for(int i=0; i<N; ++i)
std::cout << i
<< ": (" << pairs[i][0] // same as pairs[i].first()
<< ", " << pairs[i][1] // same as pairs[i].second()
<< ")" << std::endl;
return 0;
}