4

I want to convert the following code from objective C to C++.

In the class myClass, I have this attribute:

float tab[dim1][dim2][dim3];

In an objective-C file, the multidimensional array is filled from a binary file:

NSData *dataTab=[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"pathOfMyTab" ofType:@""]];
[dataTab getBytes:myClass -> tab  length:[dataTab length]];

How could I translate this part into C++ ?

PatriceG
  • 3,851
  • 5
  • 28
  • 43

4 Answers4

1

Take a look at fstream, fread and read, all read binary files, pick the approach that suits.

CRD
  • 52,522
  • 5
  • 70
  • 86
1

On my mind the simplest and fastest way is to use memcpy() to copy NSData' bytes into target array with same structure (dimensions) as a source one. See, for example:

https://github.com/Voldemarus/MultiDimensionalArrayDemo/tree/master

#import "DemoClass.h"

#define DIM1    3
#define DIM2    4
#define DIM3    2

@interface DemoClass() {
    int src[DIM1][DIM2][DIM3];  // source (initial) array

    int dst[DIM1][DIM2][DIM3];  // destination array
}
@end

@implementation DemoClass

- (instancetype) init
{
    if (self = [super init]) {
        for (int i = 0; i < DIM1; i++) {
            for (int j = 0; j < DIM2; j++) {
                for (int k = 0; k < DIM3; k++) {
                    int value = i*100 + j*10 + k;
                    src[i][j][k] = value;
                }
            }
        }
    }
    return self;
}

int getIntFromArray(int *array, int i, int j, int k) {
    int offset = j*DIM3 + i*DIM2*DIM3;
    return array[offset];
}

void putIntToArray(int *array, int i, int j, int k, int value) {
    int offset = j*DIM3 + i*DIM2*DIM3;
    array[offset] = value;
}

- (void) run
{
    // Step 1. Save array into NSData
    NSInteger s = sizeof(int)*DIM1*DIM2*DIM3;
    NSData *data = [[NSData alloc] initWithBytes:src length:s];
    NSAssert(data, @"NSData should be created");
    //Step2 - Create new array
    int *bytes = (int *)[data bytes];
    memcpy(dst,bytes,s);
    // Step 3. Compare src and dst
    for (int i = 0; i < DIM1; i++) {
        for (int j = 0; j < DIM2; j++) {
            for (int k = 0; k < DIM3; k++) {
                int template = i*100 + j*10 + k;
                int s = src[i][j][k];
                int d = dst[i][j][k];
 //               NSLog(@"i %d j %d k %d -->s = %d  d = %d",i,j,k,s,d);
                NSAssert(s == template, @"Source array should have value from template");
                NSAssert(d == s, @"Destination array should be identical to the source");
            }
        }
    }

}

@end
  • I suppose that your answer mention memcpy() to copy the array in a file that will be readable in C++ ? Could you please add the code to write this array to a file (in objective-C) ? Thanks – PatriceG May 18 '20 at 20:54
1

I am assuming that your file contains the byte-representation of the array. If this is the case, then to mimic the behaviour of your Objective-C code using only C++ (the only thing that makes this C++ is the reinterpret_cast<>, otherwise it is just straight C), you could use the following code. I have not added any error checking, but left some comments where you might want to perform some.

float tab[dim1][dim2][dim3];

CFBundleRef mainBundle = CFBundleGetMainBundle();
CFURLRef dataTabURL = CFBundleCopyResourceURL(mainBundle, CFSTR("pathOfMyTab"), NULL, NULL);

CFReadStreamRef stream = CFReadStreamCreateWithFile(NULL, dataTabURL); // check for NULL return value
CFReadStreamOpen(stream);                                              // check for errors here
CFReadStreamRead(stream, reinterpret_cast<UInt8 *>(tab), sizeof tab);  // check that this function returns the number of bytes you were expecting (sizeof tab)
CFReadStreamClose(stream);

// we own "stream" and "dataTabURL" because we obtained these through functions
// with "create" in the name, therefore we must relinquish ownership with CFRelease
CFRelease(stream);
CFRelease(dataTabURL); // ditto

If you already have the path available in a std::string, then you can use the following C++ code to mimic the behaviour of your Objective-C code:

// make sure to include this header
#include <fstream>

// ... then elsewhere in your .cpp file ...
float tab[dim1][dim2][dim3];

std::string path = "path/to/mytab"; // obtain from somewhere
std::ifstream input(path, std::ios::binary); // check that the file was successfully opened
input.read(reinterpret_cast<char *>(tab), sizeof tab); // check that input.gcount() is the number of bytes you expected

I believe in this case we have to use reinterpret_cast<> because the file contains the actual representation of the array (assuming it was previously written to the file in a similar manner).

You can use a hybrid approach, once you have the CFURLRef containing the path to the resource, you can obtain a file system representation of the URL using this function (providing a suitably sized output buffer to store the result), and from there you should be able to pass that to one of std::ifstream's constructors (although, you may need to cast to the appropriate type).

C++ doesn't support variable-length arrays (the size of arrays must be known at compile time). There is also no matrix type provided by the standard library, so if the dimensions of your table vary at run time, then you will need a completely separate approach to the one in my answer. You could consider serialising the output from Objective-C (using e.g. JSON or another format) such that the dimensions of the matrix are also written to the output, making it easier to parse the file in C++.

dreamlax
  • 93,976
  • 29
  • 161
  • 209
  • Thanks! The first part of the code is in objective-C right? (you mentioned "straight C" in the text). How can I use reinterpret_cast in objective C ? It seems unknown. – PatriceG May 18 '20 at 20:29
  • The first part is C++, it is just utilising a C-based API (CoreFoundation) to achieve the same result that your existing Objective-C code uses. If you remove the `reinterpret_cast<>` and use a straight C-style cast (e.g. `(UInt8 *)tab`), then the code will compile as C or C++, or even as Objective-C. If you really want to mix Objective-C and C++, you can use a hybrid known as Objective-C++. The easiest way to do this to rename your source files from `.m` to `.mm`, then you can intermix Objective-C and C++ syntax in the same file. – dreamlax May 18 '20 at 22:49
  • Ok, I realize that the CoreFoundation API is only on MacOs. So I can't used this part if I am programming in another environment (I am currently using Linux). I found some wrappers, but it seems overkill. – PatriceG May 19 '20 at 08:18
  • The second part works well, thanks! Indeed I know the path of the file. As my question was about a generic C++ code, you could maybe edit your answer in order to highlight this part, and if possible explain more the aim of the reinterpret_cast, that is still quite obscure for me. – PatriceG May 19 '20 at 09:38
  • Ah, I didn't realise you were trying to use C++ in a non-macOS environment. There are CoreFoundation implementations, they are overkill for what you're trying to do, which can be achieved with just plain old C++. Normally, C++ is very strict about types, and tries to prevent you from attempting to use one type as another type (for example, trying to use `long *` as an `int *`). The `input.read()` function reads from the file and writes into a `char *` buffer, but, we want it to write directly into your `float` array, so we have to "reinterpret" the `float (*)[][]` type as `char *` – dreamlax May 20 '20 at 00:44
  • Normally, `reinterpret_cast<>` should be avoided because it can mask some serious bugs. There are probably a few legitimate uses of it, but the only one I can think of is your situation right here, where we need to directly read/write an "object representation". See [here](https://en.cppreference.com/w/cpp/language/object) for a few more details. – dreamlax May 20 '20 at 00:48
0

float tab[dim1][dim2][dim3] looks like a three-dimensional array. The standard implementation is with three nested FOR loops.

So your C++ implementation can look like this:

  • read dim1, dim2, dim3 from somewhere, usually the first values in the file (for example 12 bytes, 4 bytes for each number)
  • read the rest of the file in three nested FOR loops

Something like:

    for (size_t i = 0; i < dim1; ++i) 
       for (size_t j = 0; j < dim2; ++j)
         for (size_t k = 0; k < dim3; ++k)
           tab[i][j][k] = read_float_value(inputFile);

In Objective-C you can write the file in a similar way.

Here are some examples to get you started:

Cosmin
  • 21,216
  • 5
  • 45
  • 60
  • It is indeed a three-dimensional array. But I don't know if the binary file that I read in Objective-C with NSData can be read directly with C++. What do you think? – PatriceG May 18 '20 at 20:57