一個易於使用的純頭檔案跨平台 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 整合將推遲到宿主項目。