Repository: LouisCharlesC/safe Branch: master Commit: ab21513971c5 Files: 21 Total size: 57.9 KB Directory structure: gitextract_h1y9d_x0/ ├── .clang-format ├── .github/ │ └── workflows/ │ └── all_platforms.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake/ │ ├── InstallTarget.cmake │ └── safeConfig.cmake.in ├── include/ │ └── safe/ │ ├── access_mode.h │ ├── default_locks.h │ ├── meta.h │ ├── mutable_ref.h │ └── safe.h └── tests/ ├── CMakeLists.txt ├── cmake/ │ ├── Warnings.cmake │ └── add_package.cmake ├── safe_with_custom_defaults.h ├── test_default_locks.cpp ├── test_main.cpp ├── test_readme.cpp └── test_safe.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- Language: Cpp # BasedOnStyle: Microsoft AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignArrayOfStructures: None AlignConsecutiveMacros: None AlignConsecutiveAssignments: None AlignConsecutiveBitFields: None AlignConsecutiveDeclarations: None AlignEscapedNewlines: Right AlignOperands: Align AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortEnumsOnASingleLine: false AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: MultiLine AttributeMacros: - __capability BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: false AfterClass: true AfterControlStatement: Always AfterEnum: true AfterFunction: true AfterNamespace: true AfterObjCDeclaration: true AfterStruct: true AfterUnion: false AfterExternBlock: true BeforeCatch: true BeforeElse: true BeforeLambdaBody: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeConceptDeclarations: true BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 120 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: false DisableFormat: false EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IfMacros: - KJ_IF_MAYBE IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 SortPriority: 0 CaseSensitive: false - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 SortPriority: 0 CaseSensitive: false - Regex: '.*' Priority: 1 SortPriority: 0 CaseSensitive: false IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' IndentAccessModifiers: false IndentCaseLabels: false IndentCaseBlocks: false IndentGotoLabels: true IndentPPDirectives: None IndentExternBlock: AfterExternBlock IndentRequires: false IndentWidth: 4 IndentWrappedFunctionNames: false InsertTrailingCommas: None JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true LambdaBodyIndentation: Signature MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 1000 PenaltyIndentedWhitespace: 0 PointerAlignment: Right PPIndentWidth: -1 ReferenceAlignment: Pointer ReflowComments: true ShortNamespaceLines: 1 SortIncludes: CaseSensitive SortJavaStaticImport: Before SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceAroundPointerQualifiers: Default SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: Never SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInLineCommentPrefix: Minimum: 1 Maximum: -1 SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false BitFieldColonSpacing: Both Standard: Latest StatementAttributeLikeMacros: - Q_EMIT StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TabWidth: 4 UseCRLF: false UseTab: Never WhitespaceSensitiveMacros: - STRINGIZE - PP_STRINGIZE - BOOST_PP_STRINGIZE - NS_SWIFT_NAME - CF_SWIFT_NAME ... ================================================ FILE: .github/workflows/all_platforms.yml ================================================ name: All Platforms on: [push] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, ubuntu-20.04, macos-latest, macos-13, windows-latest, windows-2019] build_type: ['Release', 'Debug'] cxx_std: [11, 20] steps: - uses: actions/checkout@v4 - name: Build with cxx standard specified run: | cmake -B ${{github.workspace}}/build/${{matrix.build_type}} -DCMAKE_CXX_STANDARD=${{matrix.cxx_std}} -DCMAKE_INSTALL_PREFIX="${{github.workspace}}/install/${{matrix.build_type}}" -DDEPS=REMOTE cmake --build ${{github.workspace}}/build/${{matrix.build_type}} --config ${{matrix.build_type}} --target install - name: Build tests with install space run: | cmake -S ${{github.workspace}}/tests -B ${{github.workspace}}/build-tests-standalone/${{matrix.build_type}} -DCMAKE_CXX_STANDARD=${{matrix.cxx_std}} -DCMAKE_PREFIX_PATH="${{github.workspace}}/install/${{matrix.build_type}}" -DDEPS=REMOTE cmake --build ${{github.workspace}}/build-tests-standalone/${{matrix.build_type}} --config ${{matrix.build_type}} - name: Test env: CTEST_OUTPUT_ON_FAILURE: 1 run: ctest -C ${{matrix.build_type}} --test-dir ${{github.workspace}}/build/${{matrix.build_type}} ================================================ FILE: .gitignore ================================================ build*/ install*/ compile_commands.json .vscode/ .cache/ Testing/ ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.9.2) cmake_policy(SET CMP0048 NEW) project(safe VERSION 1.1.0 LANGUAGES CXX) option(BUILD_TESTING "Build tests" ON) set(DEPS "AUTO" CACHE STRING "Fetch git repos or use local packages (AUTO/REMOTE/LOCAL)") set(DEPS_LIST AUTO REMOTE LOCAL) set_property(CACHE DEPS PROPERTY STRINGS ${DEPS_LIST}) if(NOT DEPS IN_LIST DEPS_LIST) message(FATAL_ERROR "DEPS must be one of ${DEPS_LIST}") endif() include(GNUInstallDirs) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") add_library(${PROJECT_NAME} INTERFACE) target_include_directories(${PROJECT_NAME} INTERFACE $ $ ) target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_11) add_library(safe::safe ALIAS safe) if(BUILD_TESTING) enable_testing() add_subdirectory(tests) endif() include(InstallTarget) install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019-2023 Louis-Charles Caron Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # *Every variable protected by a mutex should be wrapped with safe.* [![build](https://github.com/LouisCharlesC/safe/actions/workflows/all_platforms.yml/badge.svg)](https://github.com/LouisCharlesC/safe/actions/workflows/all_platforms.yml) ## Contents *safe* is a header-only library that makes code with mutexes safer and easier to understand. This readme will walk you through the important features of the library using several code examples. Read on, and enjoy safe mutexes! - [Overview](#overview) - [Motivation](#motivation) - [Main features](#main-features) - [Installation](#installation) - [Advanced usage](#advanced-usage) ## Overview Two class templates are at the core of *safe*: Safe and Access. Safe objects pack a mutex and a value object together. Access objects act as a lock (e.g. std::lock_guard) for the mutex and provide pointer-like access to the value object. Here is why you want to use *safe*: ### Mutex code without *safe* ```c++ std::string foo; // do I need to lock a mutex to safely access this variable ? std::string bar; std::string baz; // what about this one ? std::mutex fooMutex; // don't forget to change the name of this variable if foo's name changes! std::mutex barMutex; { std::lock_guard lock(fooMutex); // is this the right mutex for what I am about to do ? bar = "Hello, World!"; // Hmm, did I just to something wrong ? } std::cout << bar << std::endl; // unprotected access, is this intended ? std::cout << baz << std::endl; // what about this access ? ``` ### Mutex code with *safe* ```c++ #include "safe/safe.h" safe::Safe safeFoo; // std::string and mutex packaged together! safe::Safe safeBar; std::string baz; // now you can see that this variable has no mutex { safe::WriteAccess> foo(safeFoo); // this locks the mutex and gives you access to foo *foo = "Hello, World!"; // access the value using pointer dereference: * and -> } std::cout << safeBar.unsafe() << std::endl; // unprotected access: clearly expressed! std::cout << baz << std::endl; // all good this is just a string! ``` ## Motivation Since C++11, the standard library provides mutexes, like std::mutex, along with tools to facilitate their usage, like std::lock_guard and std::unique_lock. These are sufficient to write safe multithreaded code, but it is all too easy to write code you think is safe but actually is not. Typical mistakes are: locking the wrong mutex and accessing the value object before locking (or after unlocking) the mutex. Other minor mistakes like unnecessary locking or keeping a mutex locked for too long can also be avoided. *safe* prevents common mutex usage mistakes by providing tools that complement the C++ standard library. Using *safe*, you will find it much easier to protect a variable using a mutex, and your code will be easier to understand. No more locking the wrong mutex, no more mistaken access outside the safety of a locked mutex. No more naked shared variables, no more plain mutexes lying around and no more *mutable* keyword (ever locked a member mutex variable within a const-qualified member function ?). ## Main features ### Flexibility #### Choose the mutex and lock that fit your need The Safe class template has a template parameter for the mutex: - use std::mutex, std::shared_mutex (C++17), name it! The Access class template has a template parameter for the lock object: - use std::lock_guard, boost::shared_lock_guard, anything you want! - you can use the lock you need for every Access object you construct. #### Store the value object and mutex inside the Safe object, or refer to existing objects You can use any combination of reference and non-reference types for your Safe objects: ```c++ safe::Safe; safe::Safe; // equivalent to the above, as the second template parameter defaults to std::mutex safe::Safe; safe::Safe; safe::Safe; ``` See [this section](#with-legacy-code) for an example of using reference types to deal with legacy code. #### Flexibly construct the value object and mutex The Safe constructor accepts the arguments needed to construct the value and the mutex object. The last argument is forwarded to the mutex constructor and the rest to the value's. If the last argument cannot be used to construct the mutex, *safe* detects it and forwards everything to the value constructor. If you explicitely do not want to use the last argument to construct the mutex object, use the safe::default_construct_mutex tag as last argument. Examples: ```c++ std::mutex mutex; safe::Safe, std::mutex> bothDefault; // mutex and value are default constructed safe::Safe, std::mutex &> noDefault(42, 24, mutex); // mutex and value are initialized safe::Safe, std::mutex &> valueDefault(mutex); // mutex is initialized, and value is default constructed safe::Safe, std::mutex> mutexDefault(42, 24); // mutex is default constructed, and value is initialized safe::Safe, std::mutex> mutexDefaultTag(42, 24, safe::default_construct_mutex); // mutex is default constructed, and value is initialized ``` #### Flexibly construct the Lock objects The Access constructors have a variadic parameter pack that is forwarded to the Lock object's constructor. This can be used to pass in standard lock tags such as std::adopt_lock, but also to construct your custom locks that may require additionnal arguments than just the mutex. There are many equivalent ways to get an Access object from a Safe object. Choose the syntax you prefer: ```c++ safe::Safe safeValue; const safe::ReadAccess> value(safeValue); const safe::Safe::ReadAccess<> value(safeValue); // the empty <> are unfortunately necessary. const auto value = safeValue.readLock(); // nicer, but only with C++17 and later const auto value = safeValue.readLock(); // ok even pre-C++17, using a std::unique_lock rather than the default std::lock_guard const safe::Safe::ReadAccess value(safeValue); // equivalent to the above // All of the above exist in read-write versions, simply replace "read" by "write" (and remove the const). ``` See [this section](#with-stdlock_guard) to understand why the nicer version doesn't work pre-C++17. Examples passing arguments to the Lock object: ```c++ std::mutex aLockedMutex; aLockedMutex.lock(); safe::Safe safeValue(aLockedMutex); // given a Safe object with an already locked mutex. // Because the mutex is already locked, you need to pass the std::adopt_lock tag to std::lock_guard when you construct your Access object. // No matter how you get your Access objects, you can pass arguments to the lock's constructor. // Again, all the lines below are equivalent but here we need to specify the mutex type for the Safe type, because it is not the default (notice the &): safe::WriteAccess> value(safeValue, std::adopt_lock); safe::Safe::WriteAccess<> value(safeValue, std::adopt_lock); auto value = safeValue.writeLock(std::adopt_lock); // again, only in C++17 auto value = safeValue.writeLock(std::adopt_lock); // using a std::unique_lock still works ``` ```c++ // Here's a safe int using a timed_mutex safe::Safe safeValue; // Pass in a time-out when you lock the mutex, and it will try to lock for that duration! // safe does not do anything here, std::unique_lock does it all. safe simply forwards any argument // it gets, and when std::unique_lock receives a std::duration, it try to lock before giving up. auto value = safeValue.writeLock(std::chrono::seconds(1)); // You can reach the lock through the Access object and check whether or not the locking was successful: assert(value.lock.owns_lock()); ``` ### Even more safety! #### Choose the access mode that suits each access You will instatiate one Safe object for every value object you want to protect. But, you will create an Access object in every scope where you want to operate on the value object. For each of these accesses, you can choose whether the access is read-write or read-only. #### Force read-only access with shared_locks Shared mutexes and shared locks allow multiple reading threads to access the value object simultaneously. Unfortunately, using only mutexes and locks, the read-only restriction is not guaranteed to be applied. That is, it is possible to lock a mutex in shared mode and write to the shared value. With *safe*, you can enforce read-only access when using shared locking by using ReadAccess objects. See [this section](#enforcing-read-only-access) for details. ### Compatibility #### With legacy code You can use *safe* with old-style unsafe code that uses the soon-to-be out-of-fashion separate-mutex-and-value idiom. Imagine you are provided with the typical mutex and int. *safe* allows you to wrap these variables, without having to modify the existing code. Enjoy the safety and avoid the headaches: ```c++ std::mutex lousyMutex; int unsafeValue; // Wrap the existing variables safe::Safe safeValue(lousyMutex, unsafeValue); // do not use lousyMutex and unsafeValue directly from here on! ``` #### With code from the future *safe* is written in C++11, but it is fully compatible with mutexes and locks from different sources like C++14's std::shared_lock and C++17's std::shared_mutex, thanks to template parameters. Of course, you can also use boost::shared_lock_guard and your own custom mutexes and locks. #### With standard uses of mutexes and locks The mutex is accessible from the Safe object through an accessor functions, and the lock object is a public member of the Access class. Anything you can do with your typical mutexes and locks you can do with *safe*. For example, *safe* can seamlessly be used with std::condition_variable: ```c++ std::condition_variable cv; safe::Safe safeValue; safe::Safe::WriteAccess value(safeValue); cv.wait(value.lock, [](){return true;}); // use `value` as you usually would. ``` #### With std::lock_guard Some code shown in this readme does not compile with C++ versions prior C++17. This is because of the new rules on temporaries introduced in C++17, and because *safe* uses std::lock_guard by default. std::lock_guard is non-copiable, non-moveable so it cannot be initialized as nicely as other locks prior to C++17. If you don't like using std::lock_guard, check out the [Advanced Usage](#advanced-usage) section. ## Installation ### Method 1: Copy the source files in your project *safe* is a header-only library. Using the library can simply mean copy the contents of the include/ folder to some place of your convenience. This is the most straightforward installation method. ### Method 2: Via CMake FetchContent (CMake > 3.14) ```CMake cmake_minimum_required(VERSION 3.14) project(my_project) FetchContent_Declare( safe GIT_REPOSITORY https://github.com/LouisCharlesC/safe.git GIT_TAG v1.1.0 ) FetchContent_MakeAvailable(safe) add_executable(my_project my_project.cc) target_link_library(my_project safe::safe) ``` NOTE: `find_package(safe CONFIG REQUIRED)` is not needed with this method. ### Method 3: Install locally via CMake ```bash git clone https://github.com/louischarlescaron/safe cd safe cmake -B safe-build -DCMAKE_INSTALL_PREFIX="$(pwd)/safe-install" cmake --build safe-build --config Release --target install ``` Then you can use `find_package` in your project: ```cmake cmake_minimum_required(VERSION 3.11) project(my_project) find_package(safe CONFIG REQUIRED) add_executable(my_project my_project.cc) target_link_library(my_project safe::safe) ``` And build with: ```bash cd my_project cmake -B build -DCMAKE_PREFIX_PATH="path/to/safe-install" cmake --build build --config Release ``` NOTE: `CMAKE_PREFIX_PATH` is used to tell `find_package()` where to look for libraries. `path/to/safe-install` is not a standard path but it's easier to remove when needed. ### Method 4: Install system-wide via CMake (not recommended) ```bash git clone https://github.com/LouisCharlesC/safe cd safe cmake -B build sudo cmake --build build --config Release --target install ``` *safe* will be installed into your OS's standard intallation path. Be aware that system-wide installation make it hard to deal with multilpe library versions, and can cause collisions if you happen to install another library called safe! When you build your own project, you **won't** need to append `-DCMAKE_PREFIX_PATH="path/to/safe-install"`. ## Advanced usage ### Enforcing read-only access You can inform the *safe* library that some locks that you use are read-only (e.g. std::shared_lock, boost::shared_lock_guard). If you do so, trying to instantiate a WriteAccess object with these locks will trigger a compilation error. Use the trait class safe::AccessTraits to customize this behavior. Here is how the trait works: - If no specialization of the type trait exists for a lock type, the lock can be used with read-write and read-only Access objects. - If a specialization exists, it must declare the IsReadOnly boolean variable. - If IsReadOnly is true, the lock is read-only: constructinfg an Access object with this lock using Mode = AccessMode::ReadWrite will fail to compile. - If IsReadOnly is false, the result is the same as if no specialization exists. As an example, here is how to specialize the trait for std::shared_lock (you will find this exact code snippet in safe/access_mode.h): ```c++ template struct safe::AccessTraits> { static constexpr bool IsReadOnly = true; }; ``` ### Avoid some typing by defining your own default lock types *safe* uses std::lock_guard by default everywhere. If you know you will always use a certain lock type given some mutex type (for instance, std::unique_lock with std::timed_mutex), you can inform *safe* and it will use this lock by default with this mutex type. To do so, you must specialize the safe::DefaultLocks class template. Have a look at the tests/test_default_locks.cpp files. As you can see, you can specify a different lock type for read and write accesses: ```c++ template<> struct safe::impl::DefaultLocks { using ReadOnly = std::unique_lock; using ReadWrite = std::lock_guard; }; ``` # Acknowledgment Thanks to all contributors, issue raisers and stargazers! The cmake is inspired from https://github.com/bsamseth/cpp-project and Craig Scott's CppCon 2019 talk: Deep CMake for Library Authors. Many thanks to the authors! ================================================ FILE: cmake/InstallTarget.cmake ================================================ include(CMakePackageConfigHelpers) write_basic_package_version_file( "${PROJECT_NAME}ConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) configure_package_config_file( "${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}Config.cmake.in" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake ) install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${PROJECT_NAME}_RunTime LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${PROJECT_NAME}_RunTime # NAMELINK_COMPONENT ${PROJECT_NAME}_Development ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT NAME}_Development ) install(EXPORT ${PROJECT_NAME}Targets DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake NAMESPACE ${PROJECT_NAME}:: FILE ${PROJECT_NAME}Targets.cmake COMPONENT ${PROJECT_NAME}_Development ) ================================================ FILE: cmake/safeConfig.cmake.in ================================================ @PACKAGE_INIT@ include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") ================================================ FILE: include/safe/access_mode.h ================================================ // Copyright (c) 2019-2023 Louis-Charles Caron // This file is part of the safe library (https://github.com/LouisCharlesC/safe). // Use of this source code is governed by an MIT-style license that can be // found in the LICENSE file or at https://opensource.org/licenses/MIT. #pragma once #include #if __cplusplus >= 201402L #include #endif // __cplusplus >= 201402L namespace safe { enum class AccessMode { ReadOnly, ReadWrite }; // Base template: most LockTypes are not read only. // Specialize this template if you want to force a LockType to be read only. // If you specialize this template, consider using the pattern described in the // test_default_locks.cpp file to avoid ill-formed programs! template struct AccessTraits { static constexpr bool IsReadOnly = false; }; // Partial specialization for lock_guard: not read only. template struct AccessTraits> { static constexpr bool IsReadOnly = false; }; // Partial specialization for unique_lock: not read only. template struct AccessTraits> { static constexpr bool IsReadOnly = false; }; // Partial specialization for shared_lock: read only! #if __cplusplus >= 201402L template struct AccessTraits> { static constexpr bool IsReadOnly = true; }; #endif // __cplusplus >= 201402L } // namespace safe ================================================ FILE: include/safe/default_locks.h ================================================ // Copyright (c) 2019-2022 Louis-Charles Caron // This file is part of the safe library (https://github.com/LouisCharlesC/safe). // Use of this source code is governed by an MIT-style license that can be // found in the LICENSE file or at https://opensource.org/licenses/MIT. #pragma once #include namespace safe { namespace impl { // Base template defining default lock types for all mutex types. // Specialize this template as shown in the ReadMe and tests to define your own default locks. // This struct only uses the first template parameter (MutexType), the parameter pack is there // only to allow one to partially specialize the template using a single template parameter. Doing // so overrides the default types for any MutexType. See the test_default_locks.cpp file for examples. template struct DefaultLocks { using ReadOnly = std::lock_guard; using ReadWrite = std::lock_guard; }; } // namespace impl template using DefaultReadOnlyLockType = typename impl::DefaultLocks::ReadOnly; template using DefaultReadWriteLockType = typename impl::DefaultLocks::ReadWrite; } // namespace safe ================================================ FILE: include/safe/meta.h ================================================ // Copyright (c) 2023 Louis-Charles Caron // This file is part of the safe library (https://github.com/LouisCharlesC/safe). // Use of this source code is governed by an MIT-style license that can be // found in the LICENSE file or at https://opensource.org/licenses/MIT. #pragma once #include namespace safe { namespace impl { // This set of template and specializations is used to extract the type of the last argument of a paramter pack. template struct Last; // Base template, specializations cover all uses. template struct Last { using type = typename Last::type; }; template struct Last { using type = T; }; template <> struct Last<> { using type = void; }; template struct index_sequence {}; template struct index_sequence { using type = typename index_sequence::type; }; template struct index_sequence<0, Is...> { using type = index_sequence; }; template constexpr typename index_sequence::type make_index_sequence() { return {}; } } // namespace impl template using Last = typename impl::Last::type; } // namespace safe ================================================ FILE: include/safe/mutable_ref.h ================================================ // Copyright (c) 2019-2022 Louis-Charles Caron // This file is part of the safe library (https://github.com/LouisCharlesC/safe). // Use of this source code is governed by an MIT-style license that can be // found in the LICENSE file or at https://opensource.org/licenses/MIT. #pragma once #include namespace safe { namespace impl { /** * @brief A helper class that defines a member variable of type Type. The variable is defined "mutable Type" if Type is * not a reference, the variable is "Type&" if Type is a reference. * * @tparam Type The type of the variable to define. */ template struct MutableIfNotReference { /// Mutable Type object. mutable Type get; }; /** * @brief Specialization of MutableIfNotReference for references. * * @tparam Type The type of the reference to define. */ template struct MutableIfNotReference { /// Reference to a Type object. Type &get; }; } // namespace impl } // namespace safe ================================================ FILE: include/safe/safe.h ================================================ // Copyright (c) 2019-2023 Louis-Charles Caron // This file is part of the safe library (https://github.com/LouisCharlesC/safe). // Use of this source code is governed by an MIT-style license that can be // found in the LICENSE file or at https://opensource.org/licenses/MIT. #pragma once #include "access_mode.h" #include "default_locks.h" #include "meta.h" #include "mutable_ref.h" #include #include #if __cplusplus >= 201703L #define EXPLICIT_IF_CPP17 explicit #define EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17 ReturnType #else #define EXPLICIT_IF_CPP17 #define EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17 #endif namespace safe { namespace impl { struct DefaultConstructMutex { }; } // namespace impl /** * @brief Use this tag to default construct the mutex when constructing a Safe object. */ constexpr impl::DefaultConstructMutex default_construct_mutex; /** * @brief Wraps a value together with a mutex. * * @tparam ValueType The type of the value to protect. * @tparam MutexType The type of the mutex. */ template class Safe { private: /// Type ValueType with reference removed, if present using RemoveRefValueType = typename std::remove_reference::type; /// Type MutexType with reference removed, if present using RemoveRefMutexType = typename std::remove_reference::type; /** * @brief Manages a mutex and gives pointer-like access to a value object. * * @tparam LockType The type of the lock object that manages the mutex, example: std::lock_guard. * @tparam Mode Determines the access mode of the Access object. Can be either AccessMode::ReadOnly or * AccessMode::ReadWrite. */ template