updated example, removed multi-byte support, bugfix

This commit is contained in:
mandreyel 2017-10-08 23:14:43 +02:00
parent adf941015f
commit 61470daa08
8 changed files with 296 additions and 363 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
test/**
!test/test.cpp
!test/example.cpp

View File

@ -43,10 +43,7 @@ General usage:
#include <mio/mmap.hpp>
#include <system_error> // for std::error_code
#include <cstdio> // for std::printf
// This is just to clarify which is which when using the various mapping methods.
using offset_type = mio::mmap_sink::size_type;
using length_type = mio::mmap_sink::size_type;
#include <cassert>
int handle_error(const std::error_code& error)
{
@ -60,8 +57,8 @@ int main()
// Read-write memory map the whole file by using `map_entire_file` where the
// length of the mapping is otherwise expected, with the factory method.
std::error_code error;
mio::mmap_sink rw_mmap = mio::make_mmap_sink("file.txt",
offset_type(0), length_type(mio::map_entire_file), error);
mio::mmap_sink rw_mmap = mio::make_mmap_sink(
"file.txt", 0, mio::map_entire_file, error);
if(error) { return handle_error(error); }
// Iterate through the mapped region just as if it were any other container, and
@ -70,39 +67,31 @@ int main()
b += 10;
}
const int answer_index = rw_mmap.size() / 2;
// Or just change one value with the subscript operator.
const int answer_index = rw_mmap.size() / 2;
rw_mmap[answer_index] = 42;
// You can also create a mapping using the constructor, which throws a
// std::error_code upon failure.
const auto page_size = mio::page_size();
try {
mio::mmap_sink mmap3("another/path/to/file",
offset_type(page_size), length_type(page_size));
// ...
} catch(const std::error_code& error) {
return handle_error(error);
}
// Don't forget to flush changes to disk, which is NOT done by the destructor for
// more explicit control of this potentially expensive operation.
rw_mmap.sync();
rw_mmap.sync(error);
if(error) { handle_error(error); }
// We can then remove the mapping, after which rw_mmap will be in a default
// constructed state.
rw_mmap.close();
// constructed state, i.e. this has the same effect as if the destructor had been
// invoked.
rw_mmap.unmap();
// Now create the same mapping, but in read-only mode.
mio::mmap_source ro_mmap = mio::make_mmap_source("file.txt",
offset_type(0), length_type(mio::map_entire_file), error);
mio::mmap_source ro_mmap = mio::make_mmap_source(
"file.txt", 0, mio::map_entire_file, error);
if(error) { return handle_error(error); }
const int the_answer_to_everything = ro_mmap[answer_index];
assert(the_answer_to_everything == 42);
}
```
`mio::basic_mmap` has move-only semantics, but if multiple copies to the same mapping is required, use `mio::basic_shared_mmap`, which has `std::shared_ptr` semantics, which has the same interface as `mio::basic_mmap`.
`mio::basic_mmap` has move-only semantics, but if multiple copies to the same mapping is required, use `mio::basic_shared_mmap` which has `std::shared_ptr` semantics and has the same interface as `mio::basic_mmap`.
```c++
#include <mio/shared_mmap.hpp>
@ -113,13 +102,15 @@ mio::shared_mmap_source shared_mmap4;
shared_mmap4.map("path", offset, size_to_map, error);
```
It's possible to change the character type of the mmap object, which is useful
for wide strings or reading fixed width integers (although endianness is not
accounted for by mio, so appropriate conversions need be done by the user).
It's possible to define the character type of a byte, though aliases for the most commonly used types are provided:
```c++
using wmmap_source = mio::basic_mmap_source<wchar_t>;
using i32mmap_source = mio::basic_mmap_source<int32_t>;
using u16shared_mmap_sink = mio::basic_shared_mmap_sink<uint16_t>;
using mmap_source = basic_mmap_source<char>;
using ummap_source = basic_mmap_source<unsigned char>;
```
But it may be useful to define your own types, say when using the new `std::byte` type in C++17:
```c++
using mmap_source = mio::basic_mmap_source<std::byte>;
using mmap_sink = mio::basic_mmap_sink<std::byte>;
```
You can query the underlying system's page allocation granularity by invoking `mio::page_size()`, which is located in `mio/page.hpp`.

View File

@ -51,9 +51,9 @@ enum class access_mode
using file_handle_type = int;
#endif
template<typename CharT> struct basic_mmap
template<typename ByteT> struct basic_mmap
{
using value_type = CharT;
using value_type = ByteT;
using size_type = int64_t;
using reference = value_type&;
using const_reference = const value_type&;
@ -67,6 +67,8 @@ template<typename CharT> struct basic_mmap
using iterator_category = std::random_access_iterator_tag;
using handle_type = file_handle_type;
static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char.");
private:
// Points to the first requested byte, and not to the actual start of the mapping.
@ -82,8 +84,8 @@ private:
// Length, in bytes, requested by user, which may not be the length of the full
// mapping, and the entire length of the full mapping.
size_type num_bytes_ = 0;
size_type num_mapped_bytes_ = 0;
size_type length_ = 0;
size_type mapped_length_ = 0;
// Letting user map a file using both an existing file handle and a path introcudes
// some complexity in that we must not close the file handle if user provided it,
@ -103,15 +105,13 @@ public:
handle_type file_handle() const noexcept { return file_handle_; }
handle_type mapping_handle() const noexcept;
bool is_open() const noexcept;
bool is_open() const noexcept { return file_handle_ != INVALID_HANDLE_VALUE; }
bool is_mapped() const noexcept;
bool empty() const noexcept { return length() == 0; }
size_type offset() const noexcept { return to_char_size(num_mapped_bytes_ - num_bytes_); }
size_type length() const noexcept { return to_char_size(num_bytes_); }
size_type mapped_length() const noexcept { return to_char_size(num_mapped_bytes_); }
size_type length_in_bytes() const noexcept { return num_bytes_; }
size_type mapped_length_in_bytes() const noexcept { return num_mapped_bytes_; }
size_type offset() const noexcept { return mapped_length_ - length_; }
size_type length() const noexcept { return length_; }
size_type mapped_length() const noexcept { return mapped_length_; }
pointer data() noexcept { return data_; }
const_pointer data() const noexcept { return data_; }
@ -135,56 +135,33 @@ public:
reference operator[](const size_type i) noexcept { return data_[i]; }
const_reference operator[](const size_type i) const noexcept { return data_[i]; }
void set_length(const size_type length) noexcept;
void set_offset(const size_type offset) noexcept;
template<typename String>
void map(String& path, size_type offset, size_type num_bytes,
void map(String& path, size_type offset, size_type length,
access_mode mode, std::error_code& error);
void map(handle_type handle, size_type offset, size_type num_bytes,
void map(handle_type handle, size_type offset, size_type length,
access_mode mode, std::error_code& error);
void unmap();
void close();
void sync(std::error_code& error);
void swap(basic_mmap& other);
private:
pointer get_mapping_start() noexcept;
void map(const size_type offset, size_type num_bytes,
const access_mode mode, std::error_code& error);
static size_type to_char_size(const size_type num_bytes) noexcept
{
return num_bytes >> (sizeof(CharT) - 1);
}
static size_type to_byte_size(const size_type num_chars) noexcept
{
return num_chars << (sizeof(CharT) - 1);
}
pointer get_mapping_start() noexcept { return !data() ? nullptr : data() - offset(); }
};
template<typename CharT>
bool operator==(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b);
template<typename CharT>
bool operator!=(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b);
template<typename CharT>
bool operator<(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b);
template<typename CharT>
bool operator<=(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b);
template<typename CharT>
bool operator>(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b);
template<typename CharT>
bool operator>=(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b);
template<typename ByteT>
bool operator==(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
template<typename ByteT>
bool operator!=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
template<typename ByteT>
bool operator<(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
template<typename ByteT>
bool operator<=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
template<typename ByteT>
bool operator>(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
template<typename ByteT>
bool operator>=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
} // namespace detail
} // namespace mio

View File

@ -61,8 +61,9 @@ inline std::error_code last_error() noexcept
return error;
}
template<typename Path>
file_handle_type open_file(const Path& path, const access_mode mode, std::error_code& error)
template<typename String>
file_handle_type open_file(const String& path,
const access_mode mode, std::error_code& error)
{
error.clear();
if(detail::empty(path))
@ -111,19 +112,83 @@ inline int64_t query_file_size(file_handle_type handle, std::error_code& error)
#endif
}
struct mmap_context
{
char* data;
int64_t length;
int64_t mapped_length;
#ifdef _WIN32
file_handle_type file_mapping_handle;
#endif
};
mmap_context memory_map(const file_handle_type file_handle, const int64_t offset,
const int64_t length, const access_mode mode, std::error_code& error)
{
const int64_t aligned_offset = make_offset_page_aligned(offset);
const int64_t length_to_map = offset - aligned_offset + length;
#ifdef _WIN32
const int64_t max_file_size = offset + length;
const auto file_mapping_handle = ::CreateFileMapping(
file_handle,
0,
mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE,
int64_high(max_file_size),
int64_low(max_file_size),
0);
if(file_mapping_handle == INVALID_HANDLE_VALUE)
{
error = last_error();
return {};
}
char* mapping_start = static_cast<char*>(::MapViewOfFile(
file_mapping_handle,
mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE,
int64_high(aligned_offset),
int64_low(aligned_offset),
length_to_map));
if(mapping_start == nullptr)
{
error = last_error();
return {};
}
#else
char* mapping_start = static_cast<char*>(::mmap(
0, // Don't give hint as to where to map.
length_to_map,
mode == access_mode::read ? PROT_READ : PROT_WRITE,
MAP_SHARED,
file_handle,
aligned_offset));
if(mapping_start == MAP_FAILED)
{
error = last_error();
return {};
}
#endif
mmap_context ctx;
ctx.data = mapping_start + offset - aligned_offset;
ctx.length = length;
ctx.mapped_length = length_to_map;
#ifdef _WIN32
ctx.file_mapping_handle = file_mapping_handle;
#endif
return ctx;
}
// -- basic_mmap --
template<typename CharT>
basic_mmap<CharT>::~basic_mmap()
template<typename ByteT>
basic_mmap<ByteT>::~basic_mmap()
{
unmap();
}
template<typename CharT>
basic_mmap<CharT>::basic_mmap(basic_mmap<CharT>&& other)
template<typename ByteT>
basic_mmap<ByteT>::basic_mmap(basic_mmap<ByteT>&& other)
: data_(std::move(other.data_))
, num_bytes_(std::move(other.num_bytes_))
, num_mapped_bytes_(std::move(other.num_mapped_bytes_))
, length_(std::move(other.length_))
, mapped_length_(std::move(other.mapped_length_))
, file_handle_(std::move(other.file_handle_))
#ifdef _WIN32
, file_mapping_handle_(std::move(other.file_mapping_handle_))
@ -131,23 +196,23 @@ basic_mmap<CharT>::basic_mmap(basic_mmap<CharT>&& other)
, is_handle_internal_(std::move(other.is_handle_internal_))
{
other.data_ = nullptr;
other.num_bytes_ = other.num_mapped_bytes_ = 0;
other.length_ = other.mapped_length_ = 0;
other.file_handle_ = INVALID_HANDLE_VALUE;
#ifdef _WIN32
other.file_mapping_handle_ = INVALID_HANDLE_VALUE;
#endif
}
template<typename CharT>
basic_mmap<CharT>& basic_mmap<CharT>::operator=(basic_mmap<CharT>&& other)
template<typename ByteT>
basic_mmap<ByteT>& basic_mmap<ByteT>::operator=(basic_mmap<ByteT>&& other)
{
if(this != &other)
{
// First the existing mapping needs to be removed.
unmap();
data_ = std::move(other.data_);
num_bytes_ = std::move(other.num_bytes_);
num_mapped_bytes_ = std::move(other.num_mapped_bytes_);
length_ = std::move(other.length_);
mapped_length_ = std::move(other.mapped_length_);
file_handle_ = std::move(other.file_handle_);
#ifdef _WIN32
file_mapping_handle_ = std::move(other.file_mapping_handle_);
@ -157,7 +222,7 @@ basic_mmap<CharT>& basic_mmap<CharT>::operator=(basic_mmap<CharT>&& other)
// The moved from basic_mmap's fields need to be reset, because otherwise other's
// destructor will unmap the same mapping that was just moved into this.
other.data_ = nullptr;
other.num_bytes_ = other.num_mapped_bytes_ = 0;
other.length_ = other.mapped_length_ = 0;
other.file_handle_ = INVALID_HANDLE_VALUE;
#ifdef _WIN32
other.file_mapping_handle_ = INVALID_HANDLE_VALUE;
@ -167,8 +232,8 @@ basic_mmap<CharT>& basic_mmap<CharT>::operator=(basic_mmap<CharT>&& other)
return *this;
}
template<typename CharT>
typename basic_mmap<CharT>::handle_type basic_mmap<CharT>::mapping_handle() const noexcept
template<typename ByteT>
typename basic_mmap<ByteT>::handle_type basic_mmap<ByteT>::mapping_handle() const noexcept
{
#ifdef _WIN32
return file_mapping_handle_;
@ -177,10 +242,10 @@ typename basic_mmap<CharT>::handle_type basic_mmap<CharT>::mapping_handle() cons
#endif
}
template<typename CharT>
template<typename ByteT>
template<typename String>
void basic_mmap<CharT>::map(String& path, size_type offset,
size_type num_bytes, access_mode mode, std::error_code& error)
void basic_mmap<ByteT>::map(String& path, size_type offset,
size_type length, access_mode mode, std::error_code& error)
{
error.clear();
if(detail::empty(path))
@ -188,19 +253,19 @@ void basic_mmap<CharT>::map(String& path, size_type offset,
error = std::make_error_code(std::errc::invalid_argument);
return;
}
if(!is_open())
const auto handle = open_file(path, mode, error);
if(error) { return; }
map(handle, offset, length, mode, error);
// This MUST be after the call to map, as that sets this to true.
if(!error)
{
const auto handle = open_file(path, mode, error);
if(error) { return; }
map(handle, offset, num_bytes, mode, error);
// MUST be after the call to map, as that sets this to true.
is_handle_internal_ = true;
}
}
template<typename CharT>
void basic_mmap<CharT>::map(handle_type handle, size_type offset,
size_type num_bytes, access_mode mode, std::error_code& error)
template<typename ByteT>
void basic_mmap<ByteT>::map(handle_type handle, size_type offset,
size_type length, access_mode mode, std::error_code& error)
{
error.clear();
if(handle == INVALID_HANDLE_VALUE)
@ -212,74 +277,38 @@ void basic_mmap<CharT>::map(handle_type handle, size_type offset,
const auto file_size = query_file_size(handle, error);
if(error) { return; }
if(num_bytes <= map_entire_file)
if(length <= map_entire_file)
{
num_bytes = file_size;
length = file_size;
}
else if(offset + num_bytes > file_size)
else if(offset + length > file_size)
{
error = std::make_error_code(std::errc::invalid_argument);
return;
}
file_handle_ = handle;
is_handle_internal_ = false;
map(offset, num_bytes, mode, error);
}
template<typename CharT>
void basic_mmap<CharT>::map(const size_type offset, const size_type num_bytes,
const access_mode mode, std::error_code& error)
{
const size_type aligned_offset = make_offset_page_aligned(offset);
const size_type num_bytes_to_map = offset - aligned_offset + num_bytes;
const mmap_context ctx = memory_map(handle, offset, length, mode, error);
if(!error)
{
// We must unmap any previous mapping that may have existed prior to this call.
// Note that this must only be invoked after the new mapping has been created
// in order to provide the strong guarantee that, should a mapping fail, the
// `map` function leaves the this instance in a state as though the function
// had never been called.
unmap();
file_handle_ = handle;
is_handle_internal_ = false;
data_ = reinterpret_cast<pointer>(ctx.data);
length_ = ctx.length;
mapped_length_ = ctx.mapped_length;
#ifdef _WIN32
const size_type max_file_size = offset + num_bytes;
file_mapping_handle_ = ::CreateFileMapping(
file_handle_,
0,
mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE,
int64_high(max_file_size),
int64_low(max_file_size),
0);
if(file_mapping_handle_ == INVALID_HANDLE_VALUE)
{
error = last_error();
return;
}
const pointer mapping_start = static_cast<pointer>(::MapViewOfFile(
file_mapping_handle_,
mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE,
int64_high(aligned_offset),
int64_low(aligned_offset),
num_bytes_to_map));
if(mapping_start == nullptr)
{
error = last_error();
return;
}
#else
const pointer mapping_start = static_cast<pointer>(::mmap(
0, // Don't give hint as to where to map.
num_bytes_to_map,
mode == access_mode::read ? PROT_READ : PROT_WRITE,
MAP_SHARED,
file_handle_,
aligned_offset));
if(mapping_start == MAP_FAILED)
{
error = last_error();
return;
}
file_mapping_handle_ = ctx.file_mapping_handle;
#endif
data_ = mapping_start + offset - aligned_offset;
num_bytes_ = num_bytes;
num_mapped_bytes_ = num_bytes_to_map;
}
}
template<typename CharT>
void basic_mmap<CharT>::sync(std::error_code& error)
template<typename ByteT>
void basic_mmap<ByteT>::sync(std::error_code& error)
{
error.clear();
if(!is_open())
@ -291,10 +320,10 @@ void basic_mmap<CharT>::sync(std::error_code& error)
if(data() != nullptr)
{
#ifdef _WIN32
if(::FlushViewOfFile(get_mapping_start(), num_mapped_bytes_) == 0
if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0
|| ::FlushFileBuffers(file_handle_) == 0)
#else
if(::msync(get_mapping_start(), num_mapped_bytes_, MS_SYNC) != 0)
if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0)
#endif
{
error = last_error();
@ -309,8 +338,8 @@ void basic_mmap<CharT>::sync(std::error_code& error)
#endif
}
template<typename CharT>
void basic_mmap<CharT>::unmap()
template<typename ByteT>
void basic_mmap<ByteT>::unmap()
{
if(!is_open()) { return; }
// TODO do we care about errors here?
@ -322,7 +351,7 @@ void basic_mmap<CharT>::unmap()
file_mapping_handle_ = INVALID_HANDLE_VALUE;
}
#else
if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), num_mapped_bytes_); }
if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); }
#endif
// If file handle was obtained by our opening it (when map is called with a path,
@ -339,29 +368,15 @@ void basic_mmap<CharT>::unmap()
// Reset fields to their default values.
data_ = nullptr;
num_bytes_ = num_mapped_bytes_ = 0;
length_ = mapped_length_ = 0;
file_handle_ = INVALID_HANDLE_VALUE;
#ifdef _WIN32
file_mapping_handle_ = INVALID_HANDLE_VALUE;
#endif
}
template<typename CharT>
typename basic_mmap<CharT>::pointer
basic_mmap<CharT>::get_mapping_start() noexcept
{
if(!data()) { return nullptr; }
return data() - offset();
}
template<typename CharT>
bool basic_mmap<CharT>::is_open() const noexcept
{
return file_handle_ != INVALID_HANDLE_VALUE;
}
template<typename CharT>
bool basic_mmap<CharT>::is_mapped() const noexcept
template<typename ByteT>
bool basic_mmap<ByteT>::is_mapped() const noexcept
{
#ifdef _WIN32
return file_mapping_handle_ != INVALID_HANDLE_VALUE;
@ -370,24 +385,8 @@ bool basic_mmap<CharT>::is_mapped() const noexcept
#endif
}
template<typename CharT>
void basic_mmap<CharT>::set_length(const size_type length) noexcept
{
if(length > this->length() - offset()) { throw std::invalid_argument(""); }
num_bytes_ = to_byte_size(length);
}
template<typename CharT>
void basic_mmap<CharT>::set_offset(const size_type offset) noexcept
{
if(offset >= mapped_length()) { throw std::invalid_argument(""); }
const auto diff = offset - this->offset();
data_ += diff;
num_bytes_ += -1 * to_byte_size(diff);
}
template<typename CharT>
void basic_mmap<CharT>::swap(basic_mmap<CharT>& other)
template<typename ByteT>
void basic_mmap<ByteT>::swap(basic_mmap<ByteT>& other)
{
if(this != &other)
{
@ -397,47 +396,47 @@ void basic_mmap<CharT>::swap(basic_mmap<CharT>& other)
#ifdef _WIN32
swap(file_mapping_handle_, other.file_mapping_handle_);
#endif
swap(num_bytes_, other.num_bytes_);
swap(num_mapped_bytes_, other.num_mapped_bytes_);
swap(length_, other.length_);
swap(mapped_length_, other.mapped_length_);
swap(is_handle_internal_, other.is_handle_internal_);
}
}
template<typename CharT>
bool operator==(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b)
template<typename ByteT>
bool operator==(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b)
{
return a.data() == b.data()
&& a.size() == b.size();
}
template<typename CharT>
bool operator!=(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b)
template<typename ByteT>
bool operator!=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b)
{
return !(a == b);
}
template<typename CharT>
bool operator<(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b)
template<typename ByteT>
bool operator<(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b)
{
if(a.data() == b.data()) { return a.size() < b.size(); }
return a.data() < b.data();
}
template<typename CharT>
bool operator<=(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b)
template<typename ByteT>
bool operator<=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b)
{
return !(a > b);
}
template<typename CharT>
bool operator>(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b)
template<typename ByteT>
bool operator>(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b)
{
if(a.data() == b.data()) { return a.size() > b.size(); }
return a.data() > b.data();
}
template<typename CharT>
bool operator>=(const basic_mmap<CharT>& a, const basic_mmap<CharT>& b)
template<typename ByteT>
bool operator>=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b)
{
return !(a < b);
}

View File

@ -31,20 +31,20 @@ namespace mio {
// memory mapping. The two possible values are `read` and `write`.
using detail::access_mode;
// This value may be provided as the `num_bytes` parameter to the constructor or
// This value may be provided as the `length` parameter to the constructor or
// `map`, in which case a memory mapping of the entire file is created.
using detail::map_entire_file;
template<
access_mode AccessMode,
typename CharT
typename ByteT
> class basic_mmap
{
static_assert(AccessMode == access_mode::read
|| AccessMode == access_mode::write,
"AccessMode must be either read or write");
using impl_type = detail::basic_mmap<CharT>;
using impl_type = detail::basic_mmap<ByteT>;
impl_type impl_;
public:
@ -71,36 +71,22 @@ public:
basic_mmap() = default;
/**
* `path` must be a path to an existing file, which is then used to memory map the
* requested region. Upon failure a `std::error_code` is thrown, detailing the
* cause of the error, and the object remains in an unmapped state.
*
* When specifying `offset`, there is no need to worry about providing
* a value that is aligned with the operating system's page allocation granularity.
* This is adjusted by the implementation such that the first requested byte (as
* returned by `data` or `begin`), so long as `offset` is valid, will be at `offset`
* from the start of the file.
* The same as invoking the `map` function, except any error that may occur while
* establishing the mapping is thrown.
*/
template<typename String>
basic_mmap(const String& path, const size_type offset, const size_type num_bytes)
basic_mmap(const String& path, const size_type offset, const size_type length)
{
std::error_code error;
map(path, offset, num_bytes, error);
map(path, offset, length, error);
if(error) { throw error; }
}
/**
* `handle` must be a valid file handle, which is then used to memory map the
* requested region. Upon failure a `std::error_code` is thrown, detailing the
* cause of the error, and the object remains in an unmapped state.
*
* When specifying `offset`, there is no need to worry about providing
* a value that is aligned with the operating system's page allocation granularity.
* This is adjusted by the implementation such that the first requested byte (as
* returned by `data` or `begin`), so long as `offset` is valid, will be at `offset`
* from the start of the file.
* The same as invoking the `map` function, except any error that may occur while
* establishing the mapping is thrown.
*/
basic_mmap(const handle_type handle, const size_type offset, const size_type num_bytes)
basic_mmap(const handle_type handle, const size_type offset, const size_type length)
{
std::error_code error;
map(handle, offset, length, error);
@ -137,9 +123,9 @@ public:
/**
* `size` and `length` both return the logical length, i.e. the number of bytes
* user requested divided by `sizeof(CharT)`, while `mapped_length` returns the
* actual number of bytes that were mapped, also divided by `sizeof(CharT)`, which
* is a multiple of the underlying operating system's page allocation granularity.
* user requested to be mapped, while `mapped_length` returns the actual number of
* bytes that were mapped which is a multiple of the underlying operating system's
* page allocation granularity.
*/
size_type size() const noexcept { return impl_.length(); }
size_type length() const noexcept { return impl_.length(); }
@ -147,8 +133,7 @@ public:
/**
* Returns the offset, relative to the file's start, at which the mapping was
* requested to be created, expressed in multiples of `sizeof(CharT)` rather than
* in bytes.
* requested to be created.
*/
size_type offset() const noexcept { return impl_.offset(); }
@ -200,24 +185,6 @@ public:
reference operator[](const size_type i) noexcept { return impl_[i]; }
const_reference operator[](const size_type i) const noexcept { return impl_[i]; }
/**
* These functions can be used to alter the _conceptual_ size of the mapping.
* That is, the actual mapped memory region is not affected, just the conceptual
* range of memory on which the accessor methods ('data', 'begin', `operator[]` etc)
* operate.
*
* If `length` is larger than the number of bytes mapped minus the offset, an
* std::invalid_argument exception is thrown.
*
* `length` must be the conceptual length of the mapping, that is, the number of
* bytes mapped divided by `sizeof(CharT)`, i.e. the Container's size.
*
* If `offset` is larger than the number of bytes mapped, an std::invalid_argument
* exception is thrown.
*/
void set_length(const size_type length) noexcept { impl_.set_length(length); }
void set_offset(const size_type offset) noexcept { impl_.set_offset(offset); }
/**
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
* reason is reported via `error` and the object remains in a state as if this
@ -235,17 +202,14 @@ public:
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
* `offset` from the start of the file.
*
* `num_bytes` must be the number of bytes to map, regardless of the underlying
* value_type's size. That is, if CharT is a wide char, the value returned by
* the `size` and `length` methods is not the same as this `num_bytes` value (TODO
* this can be confusing).
* If it is `map_entire_file`, a mapping of the entire file is created.
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
* case a mapping of the entire file is created.
*/
template<typename String>
void map(const String& path, const size_type offset,
const size_type num_bytes, std::error_code& error)
const size_type length, std::error_code& error)
{
impl_.map(path, offset, num_bytes, AccessMode, error);
impl_.map(path, offset, length, AccessMode, error);
}
/**
@ -253,10 +217,9 @@ public:
* reason is reported via `error` and the object remains in a state as if this
* function hadn't been called.
*
* `path`, which must be a path to an existing file, is used to retrieve a file
* handle (which is closed when the object destructs or `unmap` is called), which is
* then used to memory map the requested region. Upon failure, `error` is set to
* indicate the reason and the object remains in an unmapped state.
* `handle`, which must be a valid file handle, which is used to memory map the
* requested region. Upon failure, `error` is set to indicate the reason and the
* object remains in an unmapped state.
*
* `offset` is the number of bytes, relative to the start of the file, where the
* mapping should begin. When specifying it, there is no need to worry about
@ -265,16 +228,13 @@ public:
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
* `offset` from the start of the file.
*
* `num_bytes` must be the number of bytes to map, regardless of the underlying
* value_type's size. That is, if CharT is a wide char, the value returned by
* the `size` and `length` methods is not the same as this `num_bytes` value (TODO
* this can be confusing).
* If it is `map_entire_file`, a mapping of the entire file is created.
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
* case a mapping of the entire file is created.
*/
void map(const handle_type handle, const size_type offset,
const size_type num_bytes, std::error_code& error)
const size_type length, std::error_code& error)
{
impl_.map(handle, offset, num_bytes, AccessMode, error);
impl_.map(handle, offset, length, AccessMode, error);
}
/**
@ -336,15 +296,15 @@ public:
* This is the basis for all read-only mmap objects and should be preferred over
* directly using basic_mmap.
*/
template<typename CharT>
using basic_mmap_source = basic_mmap<access_mode::read, CharT>;
template<typename ByteT>
using basic_mmap_source = basic_mmap<access_mode::read, ByteT>;
/**
* This is the basis for all read-write mmap objects and should be preferred over
* directly using basic_mmap.
*/
template<typename CharT>
using basic_mmap_sink = basic_mmap<access_mode::write, CharT>;
template<typename ByteT>
using basic_mmap_sink = basic_mmap<access_mode::write, ByteT>;
/**
* These aliases cover the most common use cases, both representing a raw byte stream
@ -361,10 +321,10 @@ template<
typename MMap,
typename MappingToken
> MMap make_mmap(const MappingToken& token,
int64_t offset, int64_t num_bytes, std::error_code& error)
int64_t offset, int64_t length, std::error_code& error)
{
MMap mmap;
mmap.map(token, offset, num_bytes, error);
mmap.map(token, offset, length, error);
return mmap;
}
@ -377,9 +337,9 @@ template<
*/
template<typename MappingToken>
mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset,
mmap_source::size_type num_bytes, std::error_code& error)
mmap_source::size_type length, std::error_code& error)
{
return make_mmap<mmap_source>(token, offset, num_bytes, error);
return make_mmap<mmap_source>(token, offset, length, error);
}
/**
@ -391,9 +351,9 @@ mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type o
*/
template<typename MappingToken>
mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset,
mmap_sink::size_type num_bytes, std::error_code& error)
mmap_sink::size_type length, std::error_code& error)
{
return make_mmap<mmap_sink>(token, offset, num_bytes, error);
return make_mmap<mmap_sink>(token, offset, length, error);
}
} // namespace mio

View File

@ -40,10 +40,10 @@ namespace mio {
*/
template<
access_mode AccessMode,
typename CharT
typename ByteT
> class basic_shared_mmap
{
using impl_type = basic_mmap<AccessMode, CharT>;
using impl_type = basic_mmap<AccessMode, ByteT>;
std::shared_ptr<impl_type> pimpl_;
public:
@ -92,15 +92,8 @@ public:
}
/**
* `path` must be a path to an existing file, which is then used to memory map the
* requested region. Upon failure a `std::error_code` is thrown, detailing the
* cause of the error, and the object remains in an unmapped state.
*
* When specifying `offset`, there is no need to worry about providing
* a value that is aligned with the operating system's page allocation granularity.
* This is adjusted by the implementation such that the first requested byte (as
* returned by `data` or `begin`), so long as `offset` is valid, will be at `offset`
* from the start of the file.
* The same as invoking the `map` function, except any error that may occur while
* establishing the mapping is thrown.
*/
template<typename String>
basic_shared_mmap(const String& path, const size_type offset, const size_type length)
@ -111,15 +104,8 @@ public:
}
/**
* `handle` must be a valid file handle, which is then used to memory map the
* requested region. Upon failure a `std::error_code` is thrown, detailing the
* cause of the error, and the object remains in an unmapped state.
*
* When specifying `offset`, there is no need to worry about providing
* a value that is aligned with the operating system's page allocation granularity.
* This is adjusted by the implementation such that the first requested byte (as
* returned by `data` or `begin`), so long as `offset` is valid, will be at `offset`
* from the start of the file.
* The same as invoking the `map` function, except any error that may occur while
* establishing the mapping is thrown.
*/
basic_shared_mmap(const handle_type handle, const size_type offset, const size_type length)
{
@ -153,9 +139,9 @@ public:
/**
* `size` and `length` both return the logical length, i.e. the number of bytes
* user requested divided by `sizeof(CharT)`, while `mapped_length` returns the
* actual number of bytes that were mapped, also divided by `sizeof(CharT)`, which
* is a multiple of the underlying operating system's page allocation granularity.
* user requested to be mapped, while `mapped_length` returns the actual number of
* bytes that were mapped which is a multiple of the underlying operating system's
* page allocation granularity.
*/
size_type size() const noexcept { return pimpl_->length(); }
size_type length() const noexcept { return pimpl_->length(); }
@ -163,8 +149,7 @@ public:
/**
* Returns the offset, relative to the file's start, at which the mapping was
* requested to be created, expressed in multiples of `sizeof(CharT)` rather than
* in bytes.
* requested to be created.
*/
size_type offset() const noexcept { return pimpl_->offset(); }
@ -216,24 +201,6 @@ public:
reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; }
const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; }
/**
* These functions can be used to alter the _conceptual_ size of the mapping.
* That is, the actual mapped memory region is not affected, just the conceptual
* range of memory on which the accessor methods ('data', 'begin', `operator[]` etc)
* operate.
*
* If `length` is larger than the number of bytes mapped minus the offset, an
* std::invalid_argument exception is thrown.
*
* `length` must be the conceptual length of the mapping, that is, the number of
* bytes mapped divided by `sizeof(CharT)`, i.e. the Container's size.
*
* If `offset` is larger than the number of bytes mapped, an std::invalid_argument
* exception is thrown.
*/
void set_length(const size_type length) noexcept { pimpl_->set_length(length); }
void set_offset(const size_type offset) noexcept { pimpl_->set_offset(offset); }
/**
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
* reason is reported via `error` and the object remains in a state as if this
@ -251,11 +218,8 @@ public:
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
* `offset` from the start of the file.
*
* `num_bytes` must be the number of bytes to map, regardless of the underlying
* value_type's size. That is, if CharT is a wide char, the value returned by
* the `size` and `length` methods is not the same as this `num_bytes` value (TODO
* this can be confusing).
* If it is `map_entire_file`, a mapping of the entire file is created.
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
* case a mapping of the entire file is created.
*/
template<typename String>
void map(const String& path, const size_type offset,
@ -269,10 +233,9 @@ public:
* reason is reported via `error` and the object remains in a state as if this
* function hadn't been called.
*
* `path`, which must be a path to an existing file, is used to retrieve a file
* handle (which is closed when the object destructs or `unmap` is called), which is
* then used to memory map the requested region. Upon failure, `error` is set to
* indicate the reason and the object remains in an unmapped state.
* `handle`, which must be a valid file handle, which is used to memory map the
* requested region. Upon failure, `error` is set to indicate the reason and the
* object remains in an unmapped state.
*
* `offset` is the number of bytes, relative to the start of the file, where the
* mapping should begin. When specifying it, there is no need to worry about
@ -281,11 +244,8 @@ public:
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
* `offset` from the start of the file.
*
* `num_bytes` must be the number of bytes to map, regardless of the underlying
* value_type's size. That is, if CharT is a wide char, the value returned by
* the `size` and `length` methods is not the same as this `num_bytes` value (TODO
* this can be confusing).
* If it is `map_entire_file`, a mapping of the entire file is created.
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
* case a mapping of the entire file is created.
*/
void map(const handle_type handle, const size_type offset,
const size_type length, std::error_code& error)
@ -367,15 +327,15 @@ private:
* This is the basis for all read-only mmap objects and should be preferred over
* directly using basic_shared_mmap.
*/
template<typename CharT>
using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, CharT>;
template<typename ByteT>
using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, ByteT>;
/**
* This is the basis for all read-write mmap objects and should be preferred over
* directly using basic_shared_mmap.
*/
template<typename CharT>
using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, CharT>;
template<typename ByteT>
using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, ByteT>;
/**
* These aliases cover the most common use cases, both representing a raw byte stream

49
test/example.cpp Normal file
View File

@ -0,0 +1,49 @@
#include <mio/mmap.hpp>
#include <system_error> // for std::error_code
#include <cstdio> // for std::printf
#include <cassert>
int handle_error(const std::error_code& error)
{
const auto& errmsg = error.message();
std::printf("error mapping file: %s, exiting...\n", errmsg.c_str());
return error.value();
}
int main()
{
// Read-write memory map the whole file by using `map_entire_file` where the
// length of the mapping is otherwise expected, with the factory method.
std::error_code error;
mio::mmap_sink rw_mmap = mio::make_mmap_sink(
"file.txt", 0, mio::map_entire_file, error);
if(error) { return handle_error(error); }
// Iterate through the mapped region just as if it were any other container, and
// change each byte's value (since this is a read-write mapping).
for(auto& b : rw_mmap) {
b += 10;
}
// Or just change one value with the subscript operator.
const int answer_index = rw_mmap.size() / 2;
rw_mmap[answer_index] = 42;
// Don't forget to flush changes to disk, which is NOT done by the destructor for
// more explicit control of this potentially expensive operation.
rw_mmap.sync(error);
if(error) { handle_error(error); }
// We can then remove the mapping, after which rw_mmap will be in a default
// constructed state, i.e. this has the same effect as if the destructor had been
// invoked.
rw_mmap.unmap();
// Now create the same mapping, but in read-only mode.
mio::mmap_source ro_mmap = mio::make_mmap_source(
"file.txt", 0, mio::map_entire_file, error);
if(error) { return handle_error(error); }
const int the_answer_to_everything = ro_mmap[answer_index];
assert(the_answer_to_everything == 42);
}

View File

@ -14,6 +14,12 @@ int handle_error(const std::error_code& error)
return error.value();
}
// Just make sure this compiles.
#ifdef CXX17
# include <cstddef>
using mmap_source = mio::basic_mmap_source<std::byte>;
#endif
int main()
{
const char* path = "test-file";
@ -98,15 +104,5 @@ int main()
mio::shared_ummap_source _2;
}
const auto page_size = mio::page_size();
{
// Now check if an mmap with a wider char type reports the correct size.
using u16mmap_source = mio::basic_mmap_source<char16_t>;
u16mmap_source wide_mmap = mio::make_mmap<u16mmap_source>(path, 0, page_size, error);
if(error) { return handle_error(error); }
assert(wide_mmap.size() == page_size / 2);
}
std::printf("all tests passed!\n");
}