6

I'd like to use a std::vector to control a given piece of memory. First of all I'm pretty sure this isn't good practice, but curiosity has the better of me and I'd like to know how to do this anyway.

The problem I have is a method like this:

vector<float> getRow(unsigned long rowIndex)
{
    float* row = _m->getRow(rowIndex); // row is now a piece of memory (of a known size) that I control
    vector<float> returnValue(row, row+_m->cols()); // construct a new vec from this data
    delete [] row; // delete the original memory 
    return returnValue; // return the new vector 
}

_m is a DLL interface class which returns an array of float which is the callers responsibility to delete. So I'd like to wrap this in a vector and return that to the user.... but this implementation allocates new memory for the vector, copies it, and then deletes the returned memory, then returns the vector.

What I'd like to do is to straight up tell the new vector that it has full control over this block of memory so when it gets deleted that memory gets cleaned up.

UPDATE: The original motivation for this (memory returned from a DLL) has been fairly firmly squashed by a number of responders :) However, I'd love to know the answer to the question anyway... Is there a way to construct a std::vector using a given chunk of pre-allocated memory T* array, and the size of this memory?

Jamie Cook
  • 4,375
  • 3
  • 42
  • 53
  • 1
    The code posted causes undefined behavior when you call `delete row` if the memory was allocated with `new[]` or `malloc` – Chris Dodd Jan 31 '11 at 02:58
  • I don't think you can just give a `vector` some piece of memory because `vector` needs to be able to allocate and deallocate at will as the capacity of the `vector` changes. In this situation, that requires knowledge of how your DLL allocates/deallocates memory, which of course shouldn't be required information for working with a proper DLL interface. – In silico Jan 31 '11 at 03:00
  • @ChrisDodd, typo is fixed. @Insilico I don't mind if it deallocates and reallocates this memory... I just want to be able to say "Start out using this piece of memory" – Jamie Cook Jan 31 '11 at 03:01
  • 1
    @Jamie Cook: Okay, then does your DLL interface have functions for allocating and deallocating memory? DLL interfaces should have symmetric functions for memory management, since you can't guarantee that the array returned from `getRow()` can even be deleted by `delete[]`. You should rename `getRow()` to something like `copyRow()` and create a function called `deleteRow()` or something, then provide `createRowArray()` or something so that `vector` can increase its capacity. – In silico Jan 31 '11 at 03:04
  • 3
    Do **not** have your DLL allocate memory that you are then responsible for deallocating, this can silently lead to hard-to-track-down memory corruption bugs. See http://blogs.msdn.com/b/oldnewthing/archive/2006/09/15/755966.aspx for a more detailed explanation why (short answer: different DLLs/modules can have different versions of the C/C++ runtimes loaded into them). – Adam Rosenfield Jan 31 '11 at 03:06
  • @Insilico: I'm actually writing the DLL myself, so I know that the memory allocation is of the form "float* buffer = new float[m_size];" – Jamie Cook Jan 31 '11 at 03:08
  • 1
    @Jamie Cook: That doesn't matter. The DLL should be a black box to everyone, that's the whole point of having DLLs! And what if later you want to change the memory allocation scheme to something other than `new[]/delete[]`? It'll be impossible to change because the client code is using `delete[]` all over the place. – In silico Jan 31 '11 at 03:09
  • @Insilico, @AdamRosenfield: Good point guys, even if it works on my calling code base (VS2010), calling it from a Borland code base may have gnarly behaviour :( I will fix this in my implementation but I think we've wandered a bit off topic – Jamie Cook Jan 31 '11 at 03:11
  • 1
    @Jamie Cook: Here's a better way to design DLL interfaces for this situation: Create a DLL interface function that returns how big the row is. Then, allow the application code to create its own memory buffer. The application can pass the pointer to a buffer to another DLL interface function, then the DLL fills it. – In silico Jan 31 '11 at 03:13
  • 2
    @Jamie Cook: Here's an example of what I'm talking about: [`GetWindowTextLength()`](http://msdn.microsoft.com/en-us/library/ms633521.aspx) and [`GetWindowText()`](http://msdn.microsoft.com/en-us/library/ms633520.aspx) are two functions for retrieving the title of a window in the Windows API. The application calls `GetWindowTextLength()` to allocate a big enough buffer, then passes the buffer pointer `GetWindowText()` allowing the function to fill it with text. You should design your DLL interface like that. Then it becomes easy to create a convenience function in terms of the two functions. – In silico Jan 31 '11 at 03:14
  • 1
    Also, the name `_m` (and all names beginning with an underscore) is reserved by the standard for the compiler's internal use. Use `m_` or (better yet) give it a more descriptive name if it is meant for public use. Or (even better still) don't make it publicly visible and have `getRow()` be a standalone function (in an appropriate namespace.) – Chris Lutz Jan 31 '11 at 03:26
  • 2
    @ChrisLutz: \_m is only reserved at global scope. It is *not* true that "all names beginning with an underscore" are reserved. I suspect this \_m is a data member. (If it's not a data member then it still might not be reserved if it is in a namespace.) – Fred Nurk Jan 31 '11 at 04:09

6 Answers6

5

The obvious answer is to use a custom allocator, however you might find that is really quite a heavyweight solution for what you need. If you want to do it, the simplest way is to take the allocator defined (as the default scond template argument to vector<>) by the implementation, copy that and make it work as required.

Another solution might be to define a template specialisation of vector, define as much of the interface as you actually need and implement the memory customisation.

Finally, how about defining your own container with a conforming STL interface, defining random access iterators etc. This might be quite easy given that underlying array will map nicely to vector<>, and pointers into it will map to iterators.

Comment on UPDATE: "Is there a way to construct a std::vector using a given chunk of pre-allocated memory T* array, and the size of this memory?"

Surely the simple answer here is "No". Provided you want the result to be a vector<>, then it has to support growing as required, such as through the reserve() method, and that will not be possible for a given fixed allocation. So the real question is really: what exactly do you want to achieve? Something that can be used like vector<>, or something that really does have to in some sense be a vector, and if so, what is that sense?

Keith
  • 6,756
  • 19
  • 23
4

Vector's default allocator doesn't provide this type of access to its internals. You could do it with your own allocator (vector's second template parameter), but that would change the type of the vector.

It would be much easier if you could write directly into the vector:

vector<float> getRow(unsigned long rowIndex) {
  vector<float> row (_m->cols());
  _m->getRow(rowIndex, &row[0]);  // writes _m->cols() values into &row[0]
  return row;
}

Note that &row[0] is a float* and it is guaranteed for vector to store items contiguously.

Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
  • Ideally I'd like to do it without changing the type. The point is to provide the caller of getRow() with a standard vector (but without the extra memory allocation) – Jamie Cook Jan 31 '11 at 03:04
  • 1
    +1 If the OP has access to the DLL source then this is the best way to do it. – Chris Lutz Jan 31 '11 at 03:11
  • In this case I can change the DLL source (and very likely will do) however I'm really interested in knowing if it's possible to do it without the change. – Jamie Cook Jan 31 '11 at 03:22
  • 1
    @Jamie Cook: I highly recommend that you change your DLL interface so that it works like this. It's how DLL interfaces are usually designed to handle this type of situation; see my comments to your question. – In silico Jan 31 '11 at 03:23
  • @Insilico, DLL interface has been changed.... still want to know answer to original question. Even if I never use it, I'd still like to know if it can be done. – Jamie Cook Jan 31 '11 at 03:32
  • @Jamie Cook - I'm not a C++ expert (I think C is better personally) but the consensus seems to be that it is not possible (without a custom allocator). – Chris Lutz Jan 31 '11 at 03:33
  • @JamieCook: "I'd still like to know if it can be done." => "Vector's default allocator doesn't provide this type of access to its internals." – Fred Nurk Jan 31 '11 at 04:05
1

The most important thing to know here is that different DLL/Modules have different Heaps. This means that any memory that is allocated from a DLL needs to be deleted from that DLL (it's not just a matter of compiler version or delete vs delete[] or whatever). DO NOT PASS MEMORY MANAGEMENT RESPONSIBILITY ACROSS A DLL BOUNDARY. This includes creating a std::vector in a dll and returning it. But it also includes passing a std::vector to the DLL to be filled by the DLL; such an operation is unsafe since you don't know for sure that the std::vector will not try a resize of some kind while it is being filled with values.

There are two options:

  • Define your own allocator for the std::vector class that uses an allocation function that is guaranteed to reside in the DLL/Module from which the vector was created. This can easily be done with dynamic binding (that is, make the allocator class call some virtual function). Since dynamic binding will look-up in the vtable for the function call, it is guaranteed that it will fall in the code from the DLL/Module that originally created it.

  • Don't pass the vector object to or from the DLL. You can use, for example, a function getRowBegin() and getRowEnd() that return iterators (i.e. pointers) in the row array (if it is contiguous), and let the user std::copy that into its own, local std::vector object. You could also do it the other way around, pass the iterators begin() and end() to a function like fillRowInto(begin, end).

This problem is very real, although many people neglect it without knowing. Don't underestimate it. I have personally suffered silent bugs related to this issue and it wasn't pretty! It took me months to resolve it.

I have checked in the source code, and boost::shared_ptr and boost::shared_array use dynamic binding (first option above) to deal with this.. however, they are not guaranteed to be binary compatible. Still, this could be a slightly better option (usually binary compatibility is a much lesser problem than memory management across modules).

Mikael Persson
  • 18,174
  • 6
  • 36
  • 52
  • "different DLL/Modules have different Heaps" - this is only true if you change the build settings from their default (or build with multiple compilers, which is even more complex). With the default settings (`/MD`) there's only one heap. – MSalters Jan 31 '11 at 13:14
0

Your best bet is probably a std::vector<shared_ptr<MatrixCelType>>.

Lots more details in this thread.

Community
  • 1
  • 1
Raph Levien
  • 5,088
  • 25
  • 24
0

If you're trying to change where/how the vector allocates/reallocates/deallocates memory, the allocator template parameter of the vector class is what you're looking for.

If you're simply trying to avoid the overhead of construction, copy construction, assignment, and destruction, then allow the user to instantiate the vector, then pass it to your function by reference. The user is then responsible for construction and destruction.

It sounds like what you're looking for is a form of smart pointer. One that deletes what it points to when it's destroyed. Look into the Boost libraries or roll your own in that case.

Sion Sheevok
  • 4,057
  • 2
  • 21
  • 37
0

The Boost.SmartPtr library contains a whole lot of interesting classes, some of which are dedicated to handle arrays.

For example, behold scoped_array:

int main(int argc, char* argv[])
{
  boost::scoped_array<float> array(_m->getRow(atoi(argv[1])));
  return 0;
}

The issue, of course, is that scoped_array cannot be copied, so if you really want a std::vector<float>, @Fred Nurk's is probably the best you can get.

In the ideal case you'd want the equivalent to unique_ptr but in array form, however I don't think it's part of the standard.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722