0

I'm confused about how to get several float arrays from a class using return parameters. Here's a simplified example.

class Points
{
    private:
        float *x;
        float *y;
        float *z;
    public:
        void getCoordinates (float *retx, float *rety, float *retz);
}

void Points:: getCoordinates (float *retx, float *rety, float *retz)
{
    retx=x;
    rety=y;
    retz=z;
}

When I make the assignment inside the function, the pointer values match. However, the values change as soon as I leave the function. Is the scope of the pointer somehow limited?

Based on How to properly return an array (class member) in C++?, I don't think that should be the case. The discussion suggests that not only is it possible to pass the pointer, but that the changes to memory via the pointer will effect the original instance of the class. Exactly how I would expect pointers to work.

One work around that I've tried is to use memcpy

void Points::getCoordinates (float *retx, float *rety, float *retz)
 {
memcpy(retx, x, sizeof(float)*numPoints);
memcpy(rety, y, sizeof(float)*numPoints);
memcpy(retz, z, sizeof(float)*numPoints);   
}

This works the way I want it too. The downside of this approach is that I need to allocate the correct amount of memory in the calling function, and I'm using more memory than I think I need to.

Community
  • 1
  • 1
Cecilia
  • 4,512
  • 3
  • 32
  • 75
  • you need to pass input arguments either as double pointers or as reference to pointer (i.e., you need to pass them by reference). – 101010 Jun 03 '14 at 15:01
  • A class `Points` that stores multiple arrays for x, y and z values sounds like [a bad idea](http://msmvps.com/blogs/jon_skeet/archive/2014/06/03/anti-pattern-parallel-collections.aspx). Why not use a `std::vector` instead, where `Point` has one x, y and z value? – fredoverflow Jun 03 '14 at 15:03
  • @FredOverflow I'm not actually writing a 'Points' class. It just seemed like the simplest example. :) – Cecilia Jun 03 '14 at 15:05
  • Do you want retx/y/z to be pointing to the exact same arrays as x/y/z after the function returns, or if you want to make *copies* of them? – dlf Jun 03 '14 at 15:09
  • Any advice about how to title this question so that future seekers might better be able to find it? I feel like the current title may be too general. – Cecilia Jun 03 '14 at 15:10
  • @dlf I'd rather not make copies. memcpy is simply the only way I knew that worked. – Cecilia Jun 03 '14 at 15:11
  • Ok. In that case Michael J's answer should do it. – dlf Jun 03 '14 at 15:14
  • Do not use arrays, do not use pointers, do not use memcpy, these are all extremely bad ideas. `std::vector` is enough for this example. – n. m. could be an AI Jun 03 '14 at 15:18
  • @n.m. Why are these bad ideas? – Cecilia Jun 03 '14 at 15:19
  • "Why are these bad ideas" This is almost too broad even for a separate SO question. – n. m. could be an AI Jun 03 '14 at 15:24
  • @2cents they are all very easy to get wrong, even for an experienced developer. There are a few high-performance and other specialized scenarios where you may need to resort to raw manipulation of memory, but they're rare. – dlf Jun 03 '14 at 15:24
  • I'm just switching to C++ from C, so in the past, I was always manipulating memory directly. Using C++, it seems the std::vector is [often prefered](http://stackoverflow.com/questions/6462985/c-stl-vector-vs-array-in-the-real-world) – Cecilia Jun 04 '14 at 08:27

2 Answers2

2

You need to use pointer references.

    void Points:: getCoordinates (float *&retx, float *&rety, float *&retz)
Michael J
  • 7,631
  • 2
  • 24
  • 30
  • Only thing I might add is that, depending on OP's needs, it may be better to pass `const float*&` to prevent the caller from inadvertently changing the `Points` object's internal state later using the out parameters (obviously if that behavior is *desired*, leave the `const` out). – dlf Jun 03 '14 at 15:27
2

However, the values change as soon as I leave the function. Is the scope of the pointer somehow limited?

This is as it should be.

Consider this code:

int f(int a) { a = 20; }

int x = 10;
f(x);

When you call f, a place is created on the stack, where the value in x (10) is placed. Then, this place is named to a and the function is executed for a, having the value x had. If you modify a (like in the code above), x is not modified, because x is a different variable.

Now, consider what happens when a is a pointer:

int g(int *a) { a = nullptr; }
int *b = new int{10};
g(b);

Here, the value created on the stack for a, is a memory address. If you modify the address itself (if you do a = nullptr;), similar to the case above, b is not affected, only a (i.e. the change only has effect within the function body).

You can modify the value at the address pointed to by a. Although b and a are different variables, when you call g(b), both variables (although different) will contain the same memory address. Thus, if you alter the value at the address pointed to by a, you are in effect, returning a value to the client code (you are not really returning it, but client code will have access to it, by de-referencing b).

Code:

int g(int *a) { *a = 20; } // assign value to memory pointed to, by a
int *b = new int{10};
g(b); // a and b will point to the same value
assert(*b == 20); // evaluates to true

This is the C style of using an output parameter. In C, an output parameter is a parameter holding an address, so that modifying the value at that address allows client code to access this address (like in my example above).

In your concrete situation, your code can be written like this (but probably shouldn't -- see below):

void Points::getCoordinates (float **retx, float **rety, float **retz)
{
    *retx=x;
    *rety=y;
    *retz=z;
}

Each parameter has an extra level of indirection (i.e. if you want your output parameter to hold an address to a float, you have to take as an argument a pointer to that address -- i.e. a double pointer, or a pointer to a pointer to a float).

Why you shouldn't:

C++ introduced references, which allow an address to be treated like the object itself. So, for C++, your code can look like this:

void Points::getCoordinates (float *&retx, float *&rety, float *&retz)
{
    retx=x;
    rety=y;
    retz=z;
}

Better yet, in C++ your code probably should look like this (not sure if this is feasible for your code/code base):

struct Point3d { float x, y, z; }
Point3d Points::getCoordinates() const { return Point3d{ x, y, z }; }
utnapistim
  • 26,809
  • 3
  • 46
  • 82