Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/traits.html
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,22 @@ <h3><a name="level">Implementation Level</a></h3>
<pre><code>
BOOST_CLASS_IMPLEMENTATION(my_class, boost::serialization::object_class_info)
</code></pre>
Take care when assigning <code style="white-space: normal">primitive_type</code>.
It states that the archive already handles the type itself; it is not a request
to make it do so. Archives implement primitives for the fundamental types and
for <code style="white-space: normal">std::string</code> and
<code style="white-space: normal">std::wstring</code>. For any other type they
fall back to stream extraction, in the text and xml archives, or to a copy of
<code style="white-space: normal">sizeof(T)</code> raw bytes, in the binary
ones. Assigning the level to a type an archive knows nothing about therefore
compiles, but reads back a wrong value rather than failing: declared this way,
a <code style="white-space: normal">std::pmr::string</code> stops at the first
space. A string whose allocator is not
<code style="white-space: normal">std::allocator</code> needs no such
declaration, as
<a href="../../../boost/serialization/string.hpp" target="string_hpp">string.hpp</a>
serializes it just like <code style="white-space: normal">std::string</code>.
<br><br>
If implementation level is not explicitly assigned, the system uses
a default according to the following rules.
<ul>
Expand Down
77 changes: 77 additions & 0 deletions include/boost/serialization/string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,97 @@
// serialization for stl string templates

// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
// Copyright 2026 Gennaro Prota.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// See http://www.boost.org for updates, documentation, and revision history.

#include <memory>
#include <string>

#include <boost/config.hpp>
#include <boost/core/enable_if.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/integral_c_tag.hpp>
#include <boost/type_traits/is_same.hpp>

#include <boost/serialization/level.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/split_free.hpp>

BOOST_CLASS_IMPLEMENTATION(std::string, boost::serialization::primitive_type)
#ifndef BOOST_NO_STD_WSTRING
BOOST_CLASS_IMPLEMENTATION(std::wstring, boost::serialization::primitive_type)
#endif

namespace boost {
namespace serialization {

/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// basic_string with an allocator of its own, std::pmr::string being the
// usual case. The archives implement primitives for std::string and
// std::wstring only, so such a string borrows the primitive of the
// correspondingly built default allocated string. Passing a null name to
// make_nvp keeps the representation identical to that of std::string in
// every archive, xml included, so the two interoperate.

template<class Archive, class Ch, class Tr, class Alloc>
inline typename boost::disable_if<
boost::is_same<Alloc, std::allocator<Ch> >
>::type
save(
Archive & ar,
const std::basic_string<Ch, Tr, Alloc> & t,
const unsigned int /* file_version */
){
const std::basic_string<Ch, Tr> s(t.begin(), t.end());
ar << boost::serialization::make_nvp(NULL, s);
}

template<class Archive, class Ch, class Tr, class Alloc>
inline typename boost::disable_if<
boost::is_same<Alloc, std::allocator<Ch> >
>::type
load(
Archive & ar,
std::basic_string<Ch, Tr, Alloc> & t,
const unsigned int /* file_version */
){
std::basic_string<Ch, Tr> s;
ar >> boost::serialization::make_nvp(NULL, s);
t.assign(s.begin(), s.end());
}

// split non-intrusive serialization function template into separate
// non intrusive save/load function templates
template<class Archive, class Ch, class Tr, class Alloc>
inline typename boost::disable_if<
boost::is_same<Alloc, std::allocator<Ch> >
>::type
serialize(
Archive & ar,
std::basic_string<Ch, Tr, Alloc> & t,
const unsigned int file_version
){
boost::serialization::split_free(ar, t, file_version);
}

// A string carries no class information and no version. This has to
// specialize the same template BOOST_CLASS_IMPLEMENTATION does, so that the
// full specializations naming std::string and std::wstring win over it.
template<class Ch, class Tr, class Alloc>
struct implementation_level_impl<
const std::basic_string<Ch, Tr, Alloc>
>
{
typedef mpl::integral_c_tag tag;
typedef mpl::int_<object_serializable> type;
BOOST_STATIC_CONSTANT(int, value = object_serializable);
};

} // namespace serialization
} // namespace boost

#endif // BOOST_SERIALIZATION_STRING_HPP
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ test-suite "serialization" :
[ test-bsl-run_files test_slist_ptrs : A : : [ requires slist ] ] # BOOST_HAS_SLIST ] ]
[ test-bsl-run_files test_split ]
[ test-bsl-run_files test_stack : A ]
[ test-bsl-run_files test_string_allocator ]
[ test-bsl-run_files test_tracking ]
[ test-bsl-run_files test_unregistered ]
[ test-bsl-run_files test_unique_ptr ]
Expand Down
177 changes: 177 additions & 0 deletions test/test_string_allocator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// test_string_allocator.cpp

// Copyright 2026 Gennaro Prota.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// See http://www.boost.org for updates, documentation, and revision history.

// A basic_string which does not use std::allocator, std::pmr::string being
// the usual case, has to round trip like std::string does and to produce
// the same representation, so that the two interoperate.

// Reported by schorsch1976 in
// https://github.com/boostorg/serialization/issues/267. The report showed
// that declaring such a string primitive_type silently reads back only up
// to the first space, which is what led to the support added here. Thanks
// for the clear reproducer!

#include <cstddef>
#include <cstdio>
#include <fstream>
#include <memory>
#include <string>

#include <boost/config.hpp>
#if defined(BOOST_NO_STDC_NAMESPACE)
namespace std{
using ::remove;
}
#endif

#include <boost/serialization/nvp.hpp>
#include <boost/serialization/string.hpp>

#include "test_tools.hpp"

// The smallest allocator basic_string accepts, so that the string below
// differs from std::string in its allocator only.
template<class T>
struct custom_allocator
{
typedef T value_type;
custom_allocator(){}
template<class U>
custom_allocator(const custom_allocator<U> &){}
T * allocate(std::size_t n){
return std::allocator<T>().allocate(n);
}
void deallocate(T * p, std::size_t n){
std::allocator<T>().deallocate(p, n);
}
template<class U>
bool operator==(const custom_allocator<U> &) const {
return true;
}
template<class U>
bool operator!=(const custom_allocator<U> &) const {
return false;
}
};

typedef std::basic_string<
char, std::char_traits<char>, custom_allocator<char>
> custom_string;

#ifndef BOOST_NO_STD_WSTRING
typedef std::basic_string<
wchar_t, std::char_traits<wchar_t>, custom_allocator<wchar_t>
> custom_wstring;
#endif

template<class String>
struct data
{
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int /* file_version */){
ar & BOOST_SERIALIZATION_NVP(value);
ar & BOOST_SERIALIZATION_NVP(tail);
}
String value;
int tail;
};

template<class String>
static void save(const char * testfile, const char * text){
data<String> d;
d.value = text;
d.tail = 42;
test_ostream os(testfile, TEST_STREAM_FLAGS);
test_oarchive oa(os, TEST_ARCHIVE_FLAGS);
oa << BOOST_SERIALIZATION_NVP(d);
}

template<class String>
static std::string load(const char * testfile){
data<String> d;
d.tail = 0;
test_istream is(testfile, TEST_STREAM_FLAGS);
test_iarchive ia(is, TEST_ARCHIVE_FLAGS);
ia >> BOOST_SERIALIZATION_NVP(d);
// the member after the string has to survive too: reading a string
// past its end used to leave the rest of the archive unusable
BOOST_CHECK(42 == d.tail);
return std::string(d.value.begin(), d.value.end());
}

// text which the old primitive_type route mishandled: a space stopped the
// token read, and the xml tag characters confused it further
static const char * const texts[] = {
"D1",
"a b",
"",
" lead and trail ",
"tag </value> and & < > chars"
};

static void test_roundtrip(const char * testfile, const char * text){
save<custom_string>(testfile, text);
BOOST_CHECK(load<custom_string>(testfile) == std::string(text));
}

// the representation has to match std::string's, so that an archive written
// with one loads into the other
static void test_interoperates(const char * testfile, const char * text){
save<std::string>(testfile, text);
BOOST_CHECK(load<custom_string>(testfile) == std::string(text));

save<custom_string>(testfile, text);
BOOST_CHECK(load<std::string>(testfile) == std::string(text));
}

#ifndef BOOST_NO_STD_WSTRING
// the wide string borrows the std::wstring primitive the same way
static void test_wide(const char * testfile, const wchar_t * text){
{
data<custom_wstring> d;
d.value = text;
d.tail = 42;
test_ostream os(testfile, TEST_STREAM_FLAGS);
test_oarchive oa(os, TEST_ARCHIVE_FLAGS);
oa << BOOST_SERIALIZATION_NVP(d);
}
data<custom_wstring> d;
d.tail = 0;
test_istream is(testfile, TEST_STREAM_FLAGS);
test_iarchive ia(is, TEST_ARCHIVE_FLAGS);
ia >> BOOST_SERIALIZATION_NVP(d);
BOOST_CHECK(42 == d.tail);
BOOST_CHECK(std::wstring(d.value.begin(), d.value.end())
== std::wstring(text));
}
#endif

int
test_main(int /* argc */, char * /* argv */ [])
{
const char * testfile = boost::archive::tmpnam(NULL);
BOOST_REQUIRE(NULL != testfile);

const std::size_t n = sizeof(texts) / sizeof(texts[0]);
for(std::size_t i = 0; i < n; ++i){
test_roundtrip(testfile, texts[i]);
test_interoperates(testfile, texts[i]);
}

#ifndef BOOST_NO_STD_WSTRING
test_wide(testfile, L"D1");
test_wide(testfile, L"a b");
test_wide(testfile, L"");
#endif

std::remove(testfile);
return EXIT_SUCCESS;
}