What should I pass as allocator to a std::hash_map? - c++

I have created a little class and am trying to use it as the data in an STL hash_map. Lots of searching has got me as far as knowing I need to add stuff as the Allocator, but so far I have seen no examples of how to use the default allocators in STL in my class. Could someone help and show me please? I've put a reduced version of my class below.
class cwVariable {
public:
cwVariable();
template <typename T>
cwVariable(T value)
{
stash = new byte[sizeof(T)];
memcpy(stash, &value, sizeof(T));
}
template <typename T>
void set(T value)
{
if (stash != 0) {
memcpy(stash, &value, sizeof(T));
}
}
template <typename T>
T get()
{
T out;
memcpy(&out, stash, sizeof(T));
return out;
}
~cwVariable();
private:
void * stash = 0;
};
cwVariable::~cwVariable() {
delete stash;
}
For completeness: I'm using the Arduino 1.6.4 toolchain, the STL from a port for Arduino and targetting an Arduino Mega 2560. Using the Visual Micro add-in for Visual Studio 2012.
Added later:
My real code handles more than above, but just C type string pointers, not String. The trail started when I added
std::hash_map<const char*, cwVariable> months;
to my code and got the output
cwVar.cpp.o:In function `std::hash_map<char const*, cwVariable, std::hash<char const*>,
std::equal_to<char const*>, std::allocator<cwVariable> >::operator[](char const* const&)'
stl_hash_map.h:cwVariable()'
collect2.exe*:error: ld returned 1 exit status as the error.
This made me research more and got me worried about allocators.

You don't need an allocator. Those are for when you are doing your own memory management (e.g., a memory pool). What you do need, and what your class does not have, is a working deep copy constructor. Your class instead has the freebie shallow copy constructor. This will copy the pointer rather than the contents of the pointer.
As is, your class doesn't contain the necessarily information to know how to copy construct an instance. One way to solve this would be to store the size of the allocated type as a data member. The copy constructor will allocate a stash of the right size and memcpy the source stash to the new stash of the new instance.
A much better approach would be to make the entire class a class template, and this goes after an underlying problem with your design. Your use of void* and memcpy are a recipe for future problems. As is, your class works for POD classes, but not much else.
If you templatize the entire class and change void* stash to T stash, the freebie copy constructor is exactly what you want. If you change the void* stash to T* stash, you will have to provide a copy constructor, but it's rather simple. In both cases, your get and set member functions are trivial, and your code is much more generic than is your current void* stash / memcpy implementation.

Simply using your class as a data type in hash_map does not require you to have an allocator. You only need allocator if you want to implement specific allocation policy for your map.

Related

Idiomatic way to handle T[]-like objects in C++

I am using some C-library in my C++ code. The library wants me to allocate some amount of the memory and pass the pointer to the library. Unfortunately, the exact required memory size is not known in advance, so the library also requires me to provide C-callback with the following signature:
void* callback_realloc(void* ptr, size_t new_size);
where ptr is previously passed memory and new_size is required size, and the callback must return the pointer to newly allocated memory. There is no direct way to store the allocator state. Instead, I need to rely on pointer arithmetic somehow as the following:
template<class T>
struct o_s {
std::aligned_storage_t<sizeof(T), alignof(T)> data;
};
template<class Alloc>
struct o_i: private Alloc {
std::size_t allocated_size;
const Alloc& get_allocator() const { return *this; }
};
template<class T>
struct o: public o_i, public o_s<T> {
void* ptr() {
return &data;
}
// Additionally, override class operator new and operator delete...
};
void* my_callback(void* ptr, size_t new_size) {
auto meta = static_cast<o*>(reinterpret_cast<o_s<char>*>(ptr));
// access to the allocator state ...
}
Then sizeof(o_i) + initial_size memory is allocated and ptr() is passed to the C library.
At this point, I understand that I am not the first person in the world who needs this pattern. Unfortunately (and surprisingly) I have not found anything suitable for this in Boost or STL. I would like to use ready implementation to avoid possible underwater rocks.
The simplest solution is to allocate the memory using std::malloc, and use std::realloc as the callback. C API such as this are the case where using those makes sense in C++.
You don't necessarily have to use std::malloc however. You can implement a custom allocator if you want to. Using some of the allocated storage is one way of storing allocation metadata, and it's an efficient way. That's not necessary either though, since you can also store the metadata separately in a map-like structure.
I don't think you'd find an idiomatic way to do this in C++, as this is a C idiom.
Conceptually, you have two ways of going about this:
Store the metadata alongside the buffer you've allocated (as you seem to be doing). I feel using double inheritance etc. is a bit overkill here, when you could just allocate a char buffer of sizeof(size_t)+allocation_size and use the first part for your metadata.
Allocate and return to the API a raw buffer as needed, and use a separate static data structure to manage this with a map of ptr->allocation size. I suppose this is what malloc/realloc is doing behind the scenes anyway.
Both are valid, and the one you chose depends on the specific details of your application. When dealing with memory it's ok to actually deal with memory, be it pointer arithmetic or what not.
The important thing is probably to keep the ugly bit to a single location, and provide a C++ style API to this on the C++ side of things, so that the client code isn't exposed to implementation details.

Determine the type of a pointer

So I am making a Memory Stack Allocator that is capable of allocating any instance of any type onto the heap in a continuous fashion.
In order to do this i have added a 'AllocationHeader' class directly before each allocation which contains a void* to the instance address itself and a AllocationHeader* to the previous header.
So far this has worked perfectly and i'm able to allocate and deallocate etc.
But i've ran into 1 (possibly 2) problems.
In order to deallocate properly I also need to call the destructor of the object in question and then remove it from the stack.
This is fine when i'm removing an instance that i have a pointer to as i can just pass it to the deallocate function and it knows what type it is and does its thing.
The problem lies when I want to create functions like deallocateAll() or deallocateAllAboveObject(T* obj) as I would need to know the types of each allocation without explicitly passing their pointer, so i could go down each allocation in the stack calling the deconstructor on each as I go.
What I would like is to be able to create an AllocationHeader which can store a pointer of any type, and then the type of the pointer can be retrieved at a later date without already knowing the type.
I'm really not sure how to do this as all suggestions that i've seen so far involve comparing the object with a specific type and seeing if they're the same. However I can't just check against each possible class in the program as there could be 1000's as I continue to build on this.
Alternatively if you have any suggestions for an alternative approach to a stack allocator that can deal with any type, that would be great as well.
Any help at all would be greatly appreciated.
Due to C++'s static type system, I can only see a couple of solutions to the problem
Make every type you use with the allocator derive from a (consistent) type with a virtual distructor e.g. struct destructible { virtual ~destructible() { } }, however this will potentially enlarge & alter the layout of any types you alter to derive from this.
Or uppon allocation store a function object that does the destruction, e.g. using the following template
template<typename T> void destroy(void* p) { reinterpret_cast<T*>(p)->T::~T(); }
struct AllocationHeader
{
template<typename T> AllocationHeader(AllocationHeader* previouse, void* data)
: previouse(previouse), data(data), destructor(&destroy<T>) { }
AllocationHeader* previouse;
void* data;
void (*destructor)(void*);
}
void deallocateAll(AllocationHeader& start)
{
for (AllocationHeader* a = &start; a != nullptr; a = start->previouse;)
{
a->destructor(a->data);
delete a->data;
}
}
(Without providing your code for the AllocationHeader type it is difficuilt to help you.)
Note: My compiler/IDE is currently reinstalling so I am unable to test the above code, I am pretty sure about most of it, except I may need to put a typename in the destructor call syntax reinterpret_cast<T*>(p).T::~T();
EDIT Using a template constructor, where the template arguments cannot be inferred is a bad idea, the following constructor should be used instead
AllocationHeader(AllocationHeader* previouse, void* data, void(*destructor)(void*))
: previouse(previouse), data(data), destructor(destructor) { }
Just pass &destroy<T> as the 3rd argument to it.

Casting between two type-templated classes using shared pointers

I have a class provided from a library like so:
template <typename T>
class TypedClass
{
public:
typedef typename boost::shared_ptr<TypedClass<T> > Ptr;
T m_data;
T* m_pointer_data;
};
Assuming I'm willing to accept that int and float are always the same size (and alignment) on this particular architecture, this seems valid to me:
TypedClass<int>* int_object = new TypedClass<int>();
TypedClass<float>* float_object = reinterpret_cast<TypedClass<float>* >(int_object);
Now I'm trying to achieve the same thing using boost shared_ptrs and have come up with this:
TypedClass<int>::Ptr int_object = TypedClass<int>::Ptr(new TypedClass<int>());
void* int_object_void_pointer = reinterpret_cast<void*>(int_object.get());
TypedClass<float>::Ptr float_object(reinterpret_cast<TypedClass<float>*>(int_object_void_pointer));
Which appears to work fine, but this use of shared pointers will cause the object to be deleted twice which I'd like to avoid.
Important to note that 'TypedClass' is part of a third-party library and that this library uses shared pointers for all it's internal functionality, so I need the data in this form. I have previously solved this problem inheriting from boost enable_shared_from_this, but that's not possible here.
This is a just a simple technique to attempt to reuse the same object for data types that have the same size without having to allocate a new object with the new type.
Suggestions welcome.
shared_ptr<T> has an interesting overloaded constructor:
template<class Y> shared_ptr(shared_ptr<Y> const & r, element_type * p);
Basically this constructs a shared_ptr which takes the deleter and the refcounting from r, except that it holds p.
You can use it like this:
TypedClass<int>::Ptr int_object = TypedClass<int>::Ptr(new TypedClass<int>());
TypedClass<float>::Ptr float_object(int_object,reinterpret_cast<TypedClass<float>*>(int_object.get()));
EDIT:
If you are using Boost >= 1.53.0, there is also boost::reinterpret_pointer_cast. So you can write:
TypedClass<float>::Ptr float_object = boost::reinterpret_pointer_cast<TypedClass<float> >(int_object);
If you really attempt to reuse the same object for data types that have the same size without having to allocate a new object with the new type from third-party library, you have limited choices:
You should not allocate shared_ptr from the raw pointer, otherwise it gets deleted twice, causing segment fault;
You either re-use the type (shared_ptr) so that you can directly "copy" by operator=(); or cat the raw pointer and make sure you do NOT do changes that affect the memory allocation/deletion.
As your example, I suggest code like below:
float* float_ptr = reinterpret_cast<float*>(&int_object->m_data);
// Do something with *float_ptr
// And never delete it!
You could use boost pointer cast. This is a very ugly solution but at least the ref counting will work this way.
TypedClass<int>::Ptr int_object = TypedClass<int>::Ptr(new TypedClass<int>());
TypedClass<float>::Ptr float_object = boost::static_pointer_cast<TypedClass<float>>(boost::shared_ptr<void>(int_object));
I think you can't except if you overload the shared ptr class itself with two parameters typename, as it keeps the reference to the data and deletes when the count is 0. But as you have to go from a type to another, the boost shared ptr, will think that you released the data anyway.
shared_ptr p=ptr; //adds a ref if ptr and p are of the same type.
if the type is not the same you retrieve the inner data and then release it.
another solution may be to have all the data to hold in this container using boost::any.
If TypedClass is allocated on your code (and not in the external library), you can use specific destructor to prevent multiple destruction:
template<class T>
struct NullDestructor
{
void operator()(TypedClass<T> *& t) { /* nothing to do */ }
};
template<class T>
typename TypedClass<T>::Ptr make_fake_shared_ptr( TypedClass<T> * ptr )
{
return typename TypedClass<T>::Ptr(ptr, NullDestructor<T>() );
}
TypedClass<int>::Ptr int_object = make_fake_shared_ptr<int>(new TypedClass<int>());
TypedClass<float> * ptr = reinterpret_cast<TypedClass<float>*>(int_object.get());
TypedClass<float>::Ptr float_object = make_fake_shared_ptr<float>(ptr);
With this solution, you're in charge of destructing memory manually at the end:
delete float_object.get();
You can improve this solution by using a custom allocator and a pool.

Different behaviors for different size in C++ (Firebreath source code)

I encounter a confused question when I go through the source code of firebreath (src/ScriptingCore/Variant.h)
// function pointer table
struct fxn_ptr_table {
const std::type_info& (*get_type)();
void (*static_delete)(void**);
void (*clone)(void* const*, void**);
void (*move)(void* const*,void**);
bool (*less)(void* const*, void* const*);
};
// static functions for small value-types
template<bool is_small>
struct fxns
{
template<typename T>
struct type {
static const std::type_info& get_type() {
return typeid(T);
}
static void static_delete(void** x) {
reinterpret_cast<T*>(x)->~T();
}
static void clone(void* const* src, void** dest) {
new(dest) T(*reinterpret_cast<T const*>(src));
}
static void move(void* const* src, void** dest) {
reinterpret_cast<T*>(dest)->~T();
*reinterpret_cast<T*>(dest) = *reinterpret_cast<T const*>(src);
}
static bool lessthan(void* const* left, void* const* right) {
T l(*reinterpret_cast<T const*>(left));
T r(*reinterpret_cast<T const*>(right));
return l < r;
}
};
};
// static functions for big value-types (bigger than a void*)
template<>
struct fxns<false>
{
template<typename T>
struct type {
static const std::type_info& get_type() {
return typeid(T);
}
static void static_delete(void** x) {
delete(*reinterpret_cast<T**>(x));
}
static void clone(void* const* src, void** dest) {
*dest = new T(**reinterpret_cast<T* const*>(src));
}
static void move(void* const* src, void** dest) {
(*reinterpret_cast<T**>(dest))->~T();
**reinterpret_cast<T**>(dest) = **reinterpret_cast<T* const*>(src);
}
static bool lessthan(void* const* left, void* const* right) {
return **reinterpret_cast<T* const*>(left) < **reinterpret_cast<T* const*>(right);
}
};
};
template<typename T>
struct get_table
{
static const bool is_small = sizeof(T) <= sizeof(void*);
static fxn_ptr_table* get()
{
static fxn_ptr_table static_table = {
fxns<is_small>::template type<T>::get_type
, fxns<is_small>::template type<T>::static_delete
, fxns<is_small>::template type<T>::clone
, fxns<is_small>::template type<T>::move
, fxns<is_small>::template type<T>::lessthan
};
return &static_table;
}
};
The question is why the implementation of static functions for the big value-types (bigger than void*) is different from the small ones.
For example, static_delete for small value-type is just to invoke destructor on T instance, while that for big value-type is to use 'delete'.
Is there some trick? Thanks in advance.
It looks like Firebreath uses a dedicated memory pool for its small objects, while large objects are allocated normally in the heap. Hence the different behaviour. Notice the placement new in clone() for small objects, for instance: this creates the new object in a specified memory location without allocating it. When you create an object using placement new, you must explicitly call the destructor on it before deallocating memory, and this is what static_delete() does.
Memory is not actually deallocated because, as I say, it looks like a dedicated memory pool is in use. Memory management must be performed somewhere else. This kind of memory pool is a common optimisation for small objects.
What does the internal documentation say? If the author hasn't
documented it, he probably doesn't know himself.
Judging from the code, the interface to small objects is different than
that to large objects; the pointer you pass for a small object is a
pointer to the object itself, where as the one you pass for a large
object is a pointer to a pointer to the object.
The author, however, doesn't seem to know C++ very well (and I would
avoid using any code like this). For example, in move, he explicitly
destructs the object, then assigns to it: this is guaranteed undefined
behavior, and probably won't work reliably for anything but the simplest
built-in types. Also the distinction small vs. large objects is largely
irrelevant; some “small” objects can be quite expensive to
copy. And of course, given that everything here is a template anyway,
there's absolutely no reason to use void* for anything.
I have edited your question to include a link to the original source file, since obviously most of those answering here have not read it to see what is actually going on. I admit that this is probably one of the most confusing pieces of code in FireBreath; at the time, I was trying to avoid using boost and this has worked really well.
Since then I've considered switching to boost::any (for those itching to suggest it, no, boost::variant wouldn't work and I'm not going to explain why here; ask another question if you really care) but we have customized this class a fair amount to make it exactly what we need and boost::any would be difficult to customize in a similar manner. More than anything, we've been following the old axim: if it ain't broke, don't fix it!
First of all, you should know that several C++ experts have gone over this code; yes, it uses some practices that many consider dubious, but they are very carefully considered and they are consistent and reliable on the compilers supported by FireBreath. We have done extensive testing with valgrind, visual leak detector, LeakFinder, and Rational Purify and have never found any leaks in this code. It is more than a bit confusing; it's amazing to me that people who don't understand code assume the author doesn't know C++. In this case, Christopher Diggins (who wrote the code you quoted and the original cdiggins::any class that this is taken from) seems to know C++ extremely well as evidenced by the fact that he was able to write this code. The code is used internally and is highly optimized -- perhaps more than FireBreath needs, in fact. However, it has served us well.
I will try to explain the answer to your question as best I remember; keep in mind that I don't have a lot of time and it's been awhile since I really dug in deep with this. The main reason for "small" types using a different static class is that "small" types are pretty much built-in types; an int, a char, a long, etc. Anything bigger than void* is assumed to be an object of some sort. This is an optimization to allow it to reuse memory whenever possible rather than deleting and reallocating it.
If you look at the code side-by-side it's a lot clearer. If you look at delete and clone you'll see that on "large" objects it's dynamically allocating the memory; it calls "delete" in delete and in clone it uses a regular "new". In the "small" version it just stores the memory internally and reuses it; it never "delete"s the memory, it just calls the destructor or the constructor of the correct type on the memory that it has internally. Again, this is just done for the sake of efficiency. In move on both types it calls the destructor of the old object and then assigns the new object data.
The object itself is stored as a void* because we don't actually know what type the object will be; to get the object back out you have to specify the type, in fact. This is part of what allows the container to hold absolutely any type of data. That is the reason there are so many reinterpret_cast calls there -- many people see that and say "oh, no! The author must be clueless!" However, when you have a void* that you need to dereference, that's exactly the operator that you would use.
Anyway, all of that said, cdiggins has actually put out a new version of his any class this year; I'll need to take a look at it and probably will try to pull it in to replace the current one. The trick is that I have customized the current one (primarily to add a comparison operator so it can be put in a STL container and to add convert_cast) so I need to make sure I understand the new version well enough to do that safely.
Hope that helps; the article I got it from is here: http://www.codeproject.com/KB/cpp/dynamic_typing.aspx
Note that the article has been updated and it doesn't seem to be possible to get to the old one with the original anymore.
EDIT
Since I wrote this we have confirmed some issues with the old variant class and it has been updated and replaced with one that utilizes boost::any. Thanks to dougma for most of the work on this. FireBreath 1.7 (current master branch as of the time of this writing) contains that fix.

returning std::string/std::list from dll

Short question.
I just got a dll I'm supposed to interface with.
Dll uses crt from msvcr90D.dll (notice D), and returns std::strings, std::lists, and boost::shared_ptr. Operator new/delete is not overloaded anywhere.
I assume crt mixup (msvcr90.dll in release build, or if one of components is rebuilt with newer crt, etc) is bound to cause problems eventually, and dll should be rewritten to avoid returning anything that could possibly call new/delete (i.e. anything that could call delete in my code on a block of memory that was allocated (possibly with different crt) in dll).
Am I right or not?
The main thing to keep in mind is that dlls contain code and not memory. Memory allocated belongs to the process(1). When you instantiate an object in your process, you invoke the constructor code. During that object's lifetime you will invoke other pieces of code(methods) to work on that object's memory. Then when the object is going away the destructor code is invoked.
STL Templates are not explicitly exported from the dll. The code is statically linked into each dll. So when std::string s is created in a.dll and passed to b.dll, each dll will have two different instances of the string::copy method. copy called in a.dll invokes a.dll's copy method... If we are working with s in b.dll and call copy, the copy method in b.dll will be invoked.
This is why in Simon's answer he says:
Bad things will happen unless you can
always guarantee that your entire set
of binaries is all built with the same
toolchain.
because if for some reason, string s's copy differs between a.dll and b.dll, weird things will happen. Even worse if string itself is different between a.dll and b.dll, and the destructor in one knows to clean extra memory that the other ignores... you can have difficult to track down memory leaks. Maybe even worse... a.dll might have been built against a completely different version of the STL (ie STLPort) while b.dll is built using Microsoft's STL implementation.
So what should you do? Where we work, we have strict control over the toolchain and build settings for each dll. So when we develop internal dll's, we freely transfer STL templates around. We still have problems that on rare occasion crop up because someone didn't correctly setup their project. However we find the convenience of the STL worth the occasional problem that crops up.
For exposing dlls to 3rd parties, that's another story entirely. Unless you want to strictly require specific build settings from clients, you'll want to avoid exporting STL templates. I don't recommend strictly enforcing your clients to have specific build settings... they may have another 3rd party tool that expects you to use completely opposite build settings.
(1) Yes I know static and locals are instantiated/deleted on dll load/unload.
I have this exact problem in a project I'm working on - STL classes are transmitted to and from DLLs a lot. The problem isn't just the different memory heaps - it's actually that the STL classes have no binary standard (ABI). For example, in debug builds, some STL implementations add extra debugging information to the STL classes, such that sizeof(std::vector<T>) (release build) != sizeof(std::vector<T>) (debug build). Ouch! There's no hope you can rely on binary compatibility of these classes. Besides, if your DLL was compiled in a different compiler with some other STL implementation that used other algorithms, you might have different binary format in release builds, too.
The way I've solved this problem is by using a template class called pod<T> (POD stands for Plain Old Data, like chars and ints, which usually transfer fine between DLLs). The job of this class is to package its template parameter in to a consistent binary format, and then unpackage it at the other end. For example, instead of a function in a DLL returning a std::vector<int>, you return a pod<std::vector<int>>. There's a template specialization for pod<std::vector<T>>, which mallocs a memory buffer and copies the elements. It also provides operator std::vector<T>(), so that the return value can transparently be stored back in to a std::vector, by constructing a new vector, copying its stored elements in to it, and returning it. Because it always uses the same binary format, it can be safely compiled in to separate binaries and remain binary compatible. An alternative name for pod could be make_binary_compatible.
Here's the pod class definition:
// All members are protected, because the class *must* be specialization
// for each type
template<typename T>
class pod {
protected:
pod();
pod(const T& value);
pod(const pod& copy); // no copy ctor in any pod
pod& operator=(const pod& assign);
T get() const;
operator T() const;
~pod();
};
Here's the partial specialization for pod<vector<T>> - note, partial specialization is used so this class works for any type of T. Also note, it actually is storing a memory buffer of pod<T> rather than just T - if the vector contained another STL type like std::string, we'd want that to be binary compatible too!
// Transmit vector as POD buffer
template<typename T>
class pod<std::vector<T> > {
protected:
pod(const pod<std::vector<T> >& copy); // no copy ctor
// For storing vector as plain old data buffer
typename std::vector<T>::size_type size;
pod<T>* elements;
void release()
{
if (elements) {
// Destruct every element, in case contained other cr::pod<T>s
pod<T>* ptr = elements;
pod<T>* end = elements + size;
for ( ; ptr != end; ++ptr)
ptr->~pod<T>();
// Deallocate memory
pod_free(elements);
elements = NULL;
}
}
void set_from(const std::vector<T>& value)
{
// Allocate buffer with room for pods of T
size = value.size();
if (size > 0) {
elements = reinterpret_cast<pod<T>*>(pod_malloc(sizeof(pod<T>) * size));
if (elements == NULL)
throw std::bad_alloc("out of memory");
}
else
elements = NULL;
// Placement new pods in to the buffer
pod<T>* ptr = elements;
pod<T>* end = elements + size;
std::vector<T>::const_iterator iter = value.begin();
for ( ; ptr != end; )
new (ptr++) pod<T>(*iter++);
}
public:
pod() : size(0), elements(NULL) {}
// Construct from vector<T>
pod(const std::vector<T>& value)
{
set_from(value);
}
pod<std::vector<T> >& operator=(const std::vector<T>& value)
{
release();
set_from(value);
return *this;
}
std::vector<T> get() const
{
std::vector<T> result;
result.reserve(size);
// Copy out the pods, using their operator T() to call get()
std::copy(elements, elements + size, std::back_inserter(result));
return result;
}
operator std::vector<T>() const
{
return get();
}
~pod()
{
release();
}
};
Note the memory allocation functions used are pod_malloc and pod_free - these are simply malloc and free, but using the same function between all DLLs. In my case, all DLLs use the malloc and free from the host EXE, so they are all using the same heap, which solves the heap memory issue. (Exactly how you figure this out is down to you.)
Also note you need specializations for pod<T*>, pod<const T*>, and pod for all the basic types (pod<int>, pod<short> etc), so that they can be stored in a "pod vector" and other pod containers. These should be straightforward enough to write if you understand the above example.
This method does mean copying the entire object. You can, however, pass references to pod types, since there is an operator= which is safe between binaries. There's no real pass-by-reference, though, since the only way to change a pod type is to copy it out back to its original type, change it, then repackage as a pod. Also, the copies it creates mean it's not necessarily the fastest way, but it works.
However, you can also pod-specialize your own types, which means you can effectively return complex types like std::map<MyClass, std::vector<std::string>> providing there's a specialization for pod<MyClass> and partial specializations for std::map<K, V>, std::vector<T> and std::basic_string<T> (which you only need to write once).
The end result usage looks like this. A common interface is defined:
class ICommonInterface {
public:
virtual pod<std::vector<std::string>> GetListOfStrings() const = 0;
};
A DLL might implement it as such:
pod<std::vector<std::string>> MyDllImplementation::GetListOfStrings() const
{
std::vector<std::string> ret;
// ...
// pod can construct itself from its template parameter
// so this works without any mention of pod
return ret;
}
And the caller, a separate binary, can call it as such:
ICommonInterface* pCommonInterface = ...
// pod has an operator T(), so this works again without any mention of pod
std::vector<std::string> list_of_strings = pCommonInterface->GetListOfStrings();
So once it's set up, you can use it almost as if the pod class wasn't there.
I'm not sure about "anything that could call new/delete" - this can be managed by careful use of shared pointer equivalents with appropriate allocators/deleter functions.
However in general, I wouldn't pass templates across DLL boundaries - the implementation of the template class ends up in both sides of the interface which means you can both be using a different implementation. Bad things will happen unless you can always guarantee that your entire set of binaries is all built with the same toolchain.
When I need this sort of functionality I often use a virtual interface class across the boundary. You can then provide wrappers for std::string, list etc. that allow you to safely use them via the interface. You can then control allocation etc. using your implementation, or using a shared_ptr.
Having said all this, the one thing I do use in my DLL interfaces is shared_ptr, as it's too useful not to. I haven't yet had any problems, but everything is built with the same toolchain. I'm waiting for this to bite me, as no doubt it will. See this previous question: Using shared_ptr in dll-interfaces
For std::string you can return using c_str. In the case of more complicated stuff, an option can be something like
class ContainerValueProcessor
{
public:
virtual void operator()(const trivial_type& value)=0;
};
Then (assuming you want to use std::list), you can use an interface
class List
{
public:
virtual void processItems(ContainerValueProcessor&& proc)=0;
};
Notice that List can now be implemented by any container.