Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copying objects using different allocators in C++

Tags:

c++

c++11

So I have a nice persistent allocator class persistent_alloc<T> that allows me to allocate C++ container objects and strings in persistent memory which is backed by an mmaped file that can persist from one run of my program to the next.

My problem comes when I want to do anything that mixes persistent and non persistent objects. For example, I have

typedef std::basic_string<char, std::char_traits<char>, persistent_alloc<char>> pstring;

pstring a, b, c;
std::string x, y, z;

I want to be able to do things like:

if (a == x)
    a = y;
c = z + b;

and so forth, but by default it does not work, as pstring and std::string are unrelated types. Now as far as the comparison is concerned, I can define:

template<typename Alloc1, typename Alloc2> inline bool
operator==(const std::basic_string<char, std::char_traits<char>, Alloc1> &a,
           const std::basic_string<char, std::char_traits<char>, Alloc2> &b)
{
    return strcmp(a.c_str(), b.c_str()) == 0;
}

...and now I can compare strings for equality. But adding these for every operation seems like a pain -- it seems like they SHOULD be provided by the standard library. Worse, assignment operators and copy constructors must be members and can't be defined as global inline functions like this.

Is there a reasonable way of doing this? Or do I have to effectively rewrite the entire standard library to support allocators usefully?

like image 915
Chris Dodd Avatar asked May 10 '12 18:05

Chris Dodd


1 Answers

There is a way to handle this, but you need to think outside of the box a bit. What you need is an intermediate type that is implicitly constructable from both std::string and your allocator string.

There is a proposal for such a thing before the C++ committee currently. It is based on a Google-built Apache-licensed implementation that already exists. It's called basic_string_ref; it's a template class that is basically a pointer to the first character in a string and a size, representing the length of the string. It's not a true container in the sense that it doesn't manage memory.

Which is exactly what you need.

basic_string_ref for a particular character type and traits type is implicitly constructable from a std::basic_string regardless of allocator.

All of the comparison operators can be defined in terms of basic_string_ref. Since it is implicitly constructable from std::basic_string (and virtually free to construct), it would work transparently for comparisons between differently allocated strings.

Doing assignment is rather trickier, but doable. It requires a series of conversions:

a = pstring{basic_string_ref{y}};

Not the prettiest code, of course. We would prefer to simply change the copy constructor and assignment operator of std::basic_string to be allocator agnostic. But since that's not doable, this is really the next best thing. You could even wrap it in a template function:

template<typename DestAllocator, typename SourceAllocator, typename charT, typename traits>
std::basic_string<charT, traits, DestAllocator> conv_str(const std::basic_string<charT, traits, SourceAllocator> &input)
{
  return std::basic_string<charT, traits, DestAllocator>{basic_string_ref<charT, traits>{y}};
}

Of course, if you can do that, you can just do this:

template<typename DestAllocator, typename SourceAllocator, typename charT, typename traits>
std::basic_string<charT, traits, DestAllocator> conv_str(const std::basic_string<charT, traits, SourceAllocator> &input)
{
  return std::basic_string<charT, traits, DestAllocator>{y.begin(), y.end()};
}

It'd be great if this were just part of std::basic_string, so that you wouldn't need the workarounds. But it isn't.

like image 58
Nicol Bolas Avatar answered Oct 22 '22 00:10

Nicol Bolas