views:

337

answers:

2

I'm trying to port a
int a[][]
from Java to C++. I'm using this class as a container ArrayRef for ints because it handles references, and the project uses it extensively. In the AbstractReader class I declared

const ArrayRef<int> START_END_PATTERN_;
const ArrayRef<int> MIDDLE_PATTERN_;
const ArrayRef<ArrayRef<int> > L_PATTERNS_;
const ArrayRef<ArrayRef<int> > L_AND_G_PATTERNS_;

and

static int START_END_PATTERN[];
static int MIDDLE_PATTERN[];
static int L_PATTERNS[10][4];
static int L_AND_G_PATTERNS[20][4];
Note the trailing underscore to differentiate the two variables.

I'm not sure what to do in order to initialize the two-dimensional ArrayRef. What I'm posting here will segfault because those ArrayRefs are being allocated on the stack. Anybody have a clever way to do this?

The only way I've actually managed to get it to work is using a ArrayRef< Ref<ArrayRef<int> > > by making ArrayRef inherit from Counted, which is basically a class that allows for Reference Counting in C++. But in order to access the elements I hen have to do something like *(foo[i])[j], which is slightly nastier than foo[i][j].

int AbstractReader::L\_AND\_G_PATTERNS[20][4] = {

 {3, 2, 1, 1}, // 0
 {2, 2, 2, 1}, // 1
 {2, 1, 2, 2}, // 2
 {1, 4, 1, 1}, // 3
 {1, 1, 3, 2}, // 4
 {1, 2, 3, 1}, // 5
 {1, 1, 1, 4}, // 6
 {1, 3, 1, 2}, // 7
 {1, 2, 1, 3}, // 8
 {3, 1, 1, 2},  // 9
 // G patterns

 {1, 1, 2, 3}, // 0
 {1, 2, 2, 2}, // 1
 {2, 2, 1, 2}, // 2
 {1, 1, 4, 1}, // 3
 {2, 3, 1, 1}, // 4
 {1, 3, 2, 1}, // 5
 {4, 1, 1, 1}, // 6
 {2, 1, 3, 1}, // 7
 {3, 1, 2, 1}, // 8
 {2, 1, 1, 3}  // 9
 };

 AbstractReader::AbstractReader() 
 : decodeRowStringBuffer_(ostringstream::app),
 START_END_PATTERN_(START_END_PATTERN, 3),
 MIDDLE_PATTERN_(MIDDLE_PATTERN, 5),
 L_PATTERNS_(10),
 L_AND_G_PATTERNS_(20) {

  for (int i = 0; i < 20; i++) {
   if (i < 10) {
    L_PATTERNS_[i] = ArrayRef<int> ((L_PATTERNS[i]), 4);
   }
   ArrayRef<int> lgpattern((L_AND_G_PATTERNS[i]), 4);
   L_AND_G_PATTERNS_[i] = lgpattern;
  }
 }
+1  A: 

What you have should be safe. The (stack allocated) ArrayRefs create heap allocated Arrays to back them, and then share those Arrays.

Edit: Thanks for posting Counted. Took a bit of work, but I think I see what's going on.

Solution: Don't declare L_PATTERNS_ or L_AND_G_PATTERNS_ as const. Alternately, const_cast to get the desired operator[]. E.g.

const_cast<ArrayRef<ArrayRef<int> > &>(L_PATTERNS_)[i] = ArrayRef<int> ((L_PATTERNS[i]), 4);

Rationale: In AbstractReader, you declare:

const ArrayRef<ArrayRef<int> > L_PATTERNS_;

Then in its constructor, you attempt an assignment:

AbstractReader::AbstractReader() :
{
    ...
    L_PATTERNS_[i] = ArrayRef<int> ((L_PATTERNS[i]), 4);
    ...
}

Since L_PATTERNS_ is const, L_PATTERNS_[i] invokes a method from ArrayRef<ArrayRef<int> >:

T operator[](size_t i) const { return (*array_)[i]; }

This returns a brand new copy of what was at L_PATTERNS_[i]. The assignment then occurs (into a temporary), leaving the original unchanged. When you later go back to access L_PATTERNS_[xxx], you're looking at the original, uninitialized value (which is a NULL reference/pointer). Thus the segfault.

Somewhat surprising is that ArrayRef even allows this assignment. Certainly it breaks the "Principle of Least Surprise". One would expect the compiler to issue an error. To make sure that the compiler is more helpful in the future, we need to give a slightly different definition of ArrayRef's operator[] const (Array.h:121), such as:

const T operator[](size_t i) const { return (*array_)[i]; }

or perhaps (with caveats):

const T& operator[](size_t i) const { return (*array_)[i]; }

After making either change, the compiler disallows allow the assignment. GCC, for example, reports:

error: passing 'const common::ArrayRef<int>' as 'this' argument of 'common::ArrayRef<T>& common::ArrayRef<T>::operator=(const common::ArrayRef<T>&) [with T = int]' discards qualifiers
Managu
That's what I thought, but for some reason they segfault if right after that loop I try to access the inner arrays. It gives me a EXEC_BAD_ACCESS or similar
Andres
Yup. I've got an idea why. It's a subtle bug in `ArrayRef`
Managu
Managu,Thanks a lot for your detailed examination and great explanation of the problem. I was wrongly assuming that even though it was a const, if I initialized it in the constructor list and later in the constructor, I would still be able to modify it. What seems strange is that the compiler wouldn't complain about it!I tried the const_cast, but get the following error:`error: invalid use of const_cast with type 'common::ArrayRef<common::ArrayRef<int> >', which is not a pointer, reference, nor a pointer-to-data-member type`I also tried changing the signature to the first version
Andres
Andres
Managu
A: 

Causes may be several. For instance, you don't include in your paste the "Counted" class, and at some point, a->retain() is called (line 130). This method is not shown.

Diego Sevilla
Here is the counted class http://dpaste.com/hold/88820/
Andres