一个易于使用的纯头文件跨平台 C++11 内存映射库,具有 MIT 许可证。
创建 mio 的目标是轻松包含(即无依赖项)任何需要内存映射文件 IO 的 C++ 项目,而无需引入 Boost。
请随意提出问题,我会尽力解决任何问题。
因为内存映射是继切片面包之后最好的东西!
更严重的是,编写这个库而不是使用 Boost.Iostreams 的主要动机是缺乏对使用已打开的文件句柄/描述符建立内存映射的支持。 mio 可以实现这一点。
此外,Boost.Iostreams 的解决方案要求用户精确地在页面边界处选择偏移量,这很麻烦且容易出错。另一方面,mio 在内部进行管理,接受任何偏移量并查找最近的页面边界。
尽管有一点小问题,Boost.Iostreams 使用std::shared_ptr
实现内存映射文件 IO 以提供共享语义,即使不需要,并且堆分配的开销可能是不必要和/或不需要的。在 mio 中,有两个类来涵盖两个用例:一个是仅移动的(基本上是对系统特定映射函数的零成本抽象),另一个类的行为就像其 Boost.Iostreams 对应项一样,具有共享语义。
注意:创建映射之前该文件必须存在。
将文件映射到内存的方法有3种:
std::system_error
: mio::mmap_source mmap (path, offset, size_to_map);
或者您可以省略offset
和size_to_map
参数,在这种情况下,整个文件都会被映射:
mio::mmap_source mmap (path);
std::error_code error;
mio::mmap_source mmap = mio::make_mmap_source(path, offset, size_to_map, error);
或者:
mio::mmap_source mmap = mio::make_mmap_source(path, error);
map
成员函数: std::error_code error;
mio::mmap_source mmap;
mmap.map(path, offset, size_to_map, error);
或者:
mmap.map(path, error);
注意:构造函数需要启用异常。如果您更喜欢使用-fno-exceptions
构建项目,您仍然可以使用其他方式。
此外,在每种情况下,您都可以为文件路径提供某种字符串类型,也可以使用现有的有效文件句柄。
# include < sys/types.h >
# include < sys/stat.h >
# include < fcntl.h >
# include < mio/mmap.hpp >
// #include <mio/mio.hpp> if using single header
# include < algorithm >
int main ()
{
// NOTE: error handling omitted for brevity.
const int fd = open ( " file.txt " , O_RDONLY);
mio::mmap_source mmap (fd, 0 , mio::map_entire_file);
// ...
}
但是,mio 不会检查提供的文件描述符是否与所需映射具有相同的访问权限,因此映射可能会失败。此类错误通过传递给映射函数的std::error_code
输出参数报告。
WINDOWS 用户:该库确实支持在需要字符串的函数(例如路径参数)中使用宽字符类型。
# include < mio/mmap.hpp >
// #include <mio/mio.hpp> if using single header
# include < system_error > // for std::error_code
# include < cstdio > // for std::printf
# include < cassert >
# include < algorithm >
# include < fstream >
int handle_error ( const std::error_code& error);
void allocate_file ( const std::string& path, const int size);
int main ()
{
const auto path = " file.txt " ;
// NOTE: mio does *not* create the file for you if it doesn't exist! You
// must ensure that the file exists before establishing a mapping. It
// must also be non-empty. So for illustrative purposes the file is
// created now.
allocate_file (path, 155 );
// 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 (
path, 0 , mio::map_entire_file, error);
if (error) { return handle_error (error); }
// You can use any iterator based function.
std::fill (rw_mmap. begin (), rw_mmap. end (), ' a ' );
// Or manually 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 before unmapping. However, if
// `rw_mmap` were to go out of scope at this point, the destructor would also
// automatically invoke `sync` before `unmap`.
rw_mmap. sync (error);
if (error) { return handle_error (error); }
// We can then remove the mapping, after which rw_mmap will be in a default
// constructed state, i.e. this and the above call to `sync` have the same
// effect as if the destructor had been invoked.
rw_mmap. unmap ();
// Now create the same mapping, but in read-only mode. Note that calling the
// overload without the offset and file length parameters maps the entire
// file.
mio::mmap_source ro_mmap;
ro_mmap. map (path, error);
if (error) { return handle_error (error); }
const int the_answer_to_everything = ro_mmap[answer_index];
assert (the_answer_to_everything == 42 );
}
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 ();
}
void allocate_file ( const std::string& path, const int size)
{
std::ofstream file (path);
std::string s (size, ' 0 ' );
file << s;
}
mio::basic_mmap
是仅移动的,但如果需要同一映射的多个副本,请使用mio::basic_shared_mmap
,它具有std::shared_ptr
语义,并且与mio::basic_mmap
具有相同的接口。
# include < mio/shared_mmap.hpp >
mio::shared_mmap_source shared_mmap1 ( " path " , offset, size_to_map);
mio::shared_mmap_source shared_mmap2 (std::move(mmap1)); // or use operator=
mio::shared_mmap_source shared_mmap3 (std::make_shared<mio::mmap_source>(mmap1)); // or use operator=
mio::shared_mmap_source shared_mmap4;
shared_mmap4.map( " path " , offset, size_to_map, error);
可以定义字节的类型(必须与char
具有相同的宽度),尽管默认情况下提供了最常见的别名:
using mmap_source = basic_mmap_source< char >;
using ummap_source = basic_mmap_source< unsigned char >;
using mmap_sink = basic_mmap_sink< char >;
using ummap_sink = basic_mmap_sink< unsigned char >;
但定义自己的类型可能很有用,例如在 C++17 中使用新的std::byte
类型时:
using mmap_source = mio::basic_mmap_source<std::byte>;
using mmap_sink = mio::basic_mmap_sink<std::byte>;
尽管通常不需要,但由于 mio 将用户请求的偏移量映射到页面边界,因此您可以通过调用mio::page_size()
(位于mio/page.hpp
)来查询底层系统的页面分配粒度。
只需包含single_includemiomio.hpp
即可将 Mio 作为单个头文件添加到您的项目中。通过运行third_party
中的amalgamate.py
脚本,可以随时重新生成单个头文件。
python amalgamate.py -c config.json -s ../include
作为一个仅包含头文件的库,mio 没有编译组件。尽管如此,还是提供了 CMake 构建系统,以便在许多平台和操作系统上轻松测试、安装和子项目组合。
Mio 随一小套测试和示例一起发布。当 mio 配置为最高级别的 CMake 项目时,默认情况下会构建这套可执行文件。 Mio 的测试可执行文件与 CMake 测试驱动程序 CTest 集成。
CMake 支持许多用于编译和链接的后端。
要使用静态配置构建工具,例如 GNU Make 或 Ninja:
cd < mio source directory >
mkdir build
cd build
# Configure the build
cmake -D CMAKE_BUILD_TYPE= < Debug | Release >
-G < " Unix Makefiles " | " Ninja " > ..
# build the tests
< make | ninja | cmake --build . >
# run the tests
< make test | ninja test | cmake --build . --target test | ctest >
要使用动态配置构建工具,例如 Visual Studio 或 Xcode:
cd < mio source directory >
mkdir build
cd build
# Configure the build
cmake -G < " Visual Studio 14 2015 Win64 " | " Xcode " > ..
# build the tests
cmake --build . --config < Debug | Release >
# run the tests via ctest...
ctest --build-config < Debug | Release >
# ... or via CMake build tool mode...
cmake --build . --config < Debug | Release > --target test
当然,打开配置步骤中生成的项目文件后,也可以在 IDE 中分别通过all和test目标执行构建和测试步骤。
Mio 的测试还配置为作为 CDash 软件质量仪表板应用程序的客户端运行。有关此操作模式的更多信息,请参阅 Kitware 文档。
Mio 的构建系统通过 CMake 的find_package
内在函数提供安装目标并支持下游消费。 CMake 允许安装到任意位置,可以通过在配置时定义CMAKE_INSTALL_PREFIX
来指定。如果没有用户指定,CMake 将根据平台操作系统将 mio 安装到常规位置。
要使用静态配置构建工具,例如 GNU Make 或 Ninja:
cd < mio source directory >
mkdir build
cd build
# Configure the build
cmake [-D CMAKE_INSTALL_PREFIX = " path/to/installation " ]
[-D BUILD_TESTING = False]
-D CMAKE_BUILD_TYPE=Release
-G < " Unix Makefiles " | " Ninja " > ..
# install mio
< make install | ninja install | cmake --build . --target install >
要使用动态配置构建工具,例如 Visual Studio 或 Xcode:
cd < mio source directory >
mkdir build
cd build
# Configure the project
cmake [-D CMAKE_INSTALL_PREFIX = " path/to/installation " ]
[-D BUILD_TESTING = False]
-G < " Visual Studio 14 2015 Win64 " | " Xcode " > ..
# install mio
cmake --build . --config Release --target install
请注意,如果安装根目录位于主目录之外,则安装序列的最后一个命令可能需要管理员权限(例如sudo
)。
这个安装
include/mio
子目录中share/cmake/mio
子目录后一步允许下游 CMake 项目通过find_package
使用 mio ,例如
find_package ( mio REQUIRED )
target_link_libraries ( MyTarget PUBLIC mio::mio )
WINDOWS 用户: mio::mio
目标#define
s WIN32_LEAN_AND_MEAN
和NOMINMAX
。前者确保 Win API 的导入表面积最小,后者禁用 Windows 的min
和max
宏,这样它们就不会干扰std::min
和std::max
。由于mio是一个仅包含头文件的库,因此这些定义将泄漏到下游 CMake 构建中。如果它们的存在导致您的构建出现问题,那么您可以使用替代的mio::mio_full_winapi
目标,该目标不会添加任何这些定义。
如果 mio 安装到非常规位置,下游项目可能需要通过以下任一方式指定 mio 安装根目录
CMAKE_PREFIX_PATH
配置选项,CMAKE_PREFIX_PATH
环境变量,或者mio_DIR
环境变量。请参阅 Kitware 文档以获取更多信息。
此外,mio 支持通过 CPack 打包可重定位安装。配置完成后,从构建目录中,按如下方式调用 cpack 来生成打包安装:
cpack -G < generator name > -C Release
支持的生成器列表因平台而异。请参阅cpack --help
的输出,获取平台上支持的生成器的完整列表。
要将 mio 用作子项目,请将 mio 存储库复制到项目的 dependency/externals 文件夹中。如果您的项目使用 git 进行版本控制,则可以使用 git 子模块或 git 子树与 updstream 存储库同步。这些 git 工具的使用和相对优势超出了本文档的范围,但简而言之,每个工具都可以按如下方式建立:
# via git submodule
cd < my project ' s dependencies directory>
git submodule add -b master https://github.com/mandreyel/mio.git
# via git subtree
cd <my project ' s root directory >
git subtree add --prefix < path/to/dependencies > /mio
https://github.com/mandreyel/mio.git master --squash
给定项目中的 mio 子目录,只需将以下行添加到项目中,即可将 mio 包含目录添加到目标的包含路径中。
add_subdirectory ( path /to/mio/ )
target_link_libraries ( MyTarget PUBLIC <mio::mio | mio> )
请注意,作为子项目,mio 的测试和示例将不会构建,并且 CPack 集成将推迟到宿主项目。