├── .gitignore ├── test ├── src │ ├── main.cpp │ └── scope │ │ ├── scope_exit.test.cpp │ │ ├── scope_fail.test.cpp │ │ ├── scope_success.test.cpp │ │ └── unique_resource.test.cpp └── CMakeLists.txt ├── cmake └── ScopeConfig.cmake.in ├── .appveyor ├── test.cmd ├── install.cmd └── before-build.cmd ├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── feature_request.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md └── CONTRIBUTING.md ├── .conan └── test_package │ ├── CMakeLists.txt │ ├── src │ └── main.cpp │ └── conanfile.py ├── tools └── run-doxygen.py ├── .travis ├── before-script.sh ├── install.sh ├── build.sh ├── deploy-conan.sh └── deploy-doxygen.sh ├── doc ├── legal.md ├── installing.md └── tutorial.md ├── .appveyor.yml ├── LICENSE ├── conanfile.py ├── CODE_OF_CONDUCT.md ├── CMakeLists.txt ├── .travis.yml ├── README.md └── include └── scope └── scope.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /test/src/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | 4 | -------------------------------------------------------------------------------- /cmake/ScopeConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | if (NOT TARGET Scope::Scope) 4 | include("${CMAKE_CURRENT_LIST_DIR}/ScopeTargets.cmake") 5 | endif () -------------------------------------------------------------------------------- /.appveyor/test.cmd: -------------------------------------------------------------------------------- 1 | SETLOCAL EnableDelayedExpansion 2 | 3 | @REM # Test the project 4 | 5 | cd build 6 | 7 | ctest -j 4 -C %CONFIGURATION% || exit /b !ERRORLEVEL! -------------------------------------------------------------------------------- /.appveyor/install.cmd: -------------------------------------------------------------------------------- 1 | SETLOCAL EnableDelayedExpansion 2 | 3 | @REM # Install 'conan' 4 | 5 | echo "Downloading conan..." 6 | set PATH=%PATH%;%PYTHON%/Scripts/ 7 | pip.exe install conan 8 | conan user # Create the conan data directory 9 | conan --version -------------------------------------------------------------------------------- /.appveyor/before-build.cmd: -------------------------------------------------------------------------------- 1 | SETLOCAL EnableDelayedExpansion 2 | 3 | @REM # Install 'conan' dependencies 4 | 5 | mkdir build 6 | cd build 7 | conan install .. 8 | 9 | @REM # Generate the CMake project 10 | 11 | cmake .. -A%PLATFORM% -DSCOPE_ENABLE_UNIT_TESTS=On || exit /b !ERRORLEVEL! -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question to the maintainers 4 | 5 | --- 6 | 7 | ### Checklist 8 | 9 | - [ ] This question has not yet been asked in the Github Issues section. 10 | 11 | ---------- 12 | 13 | ### Description 14 | 17 | -------------------------------------------------------------------------------- /.conan/test_package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(Scope.ConanTestPackage) 4 | 5 | include("${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") 6 | conan_set_find_paths() 7 | 8 | find_package(Scope REQUIRED) 9 | 10 | set(source_files 11 | src/main.cpp 12 | ) 13 | 14 | add_executable(${PROJECT_NAME} 15 | ${source_files} 16 | ) 17 | target_link_libraries(${PROJECT_NAME} 18 | PRIVATE Scope::Scope 19 | ) -------------------------------------------------------------------------------- /tools/run-doxygen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Script for generating doxygen output 4 | """ 5 | 6 | def root_path(): 7 | import os 8 | 9 | path = os.path.realpath(__file__) # ./tools/run-doxygen.py 10 | path = os.path.dirname(path) # ./tools/ 11 | path = os.path.dirname(path) # ./ 12 | 13 | return path 14 | 15 | 16 | if __name__ == "__main__": 17 | 18 | import subprocess 19 | import os 20 | 21 | doxyfile_path = os.path.join(root_path(),".codedocs") 22 | 23 | subprocess.run(["doxygen", doxyfile_path], 24 | cwd=root_path(), 25 | check=True) 26 | -------------------------------------------------------------------------------- /.conan/test_package/src/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | namespace { 5 | void example_deleter(int&){} 6 | } 7 | 8 | int main() 9 | { 10 | { 11 | auto scope = ::scope::make_scope_exit([]{ 12 | 13 | }); 14 | (void) scope; 15 | } 16 | { 17 | auto scope = ::scope::make_scope_fail([]{ 18 | 19 | }); 20 | (void) scope; 21 | } 22 | { 23 | auto scope = ::scope::make_scope_success([]{ 24 | 25 | }); 26 | (void) scope; 27 | } 28 | { 29 | auto resource = ::scope::make_unique_resource(int{5}, &::example_deleter); 30 | 31 | (void) resource; 32 | } 33 | 34 | return 0; 35 | } -------------------------------------------------------------------------------- /.travis/before-script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ################################################################################ 3 | # Travis-CI : before-script 4 | # ------------------------- 5 | # 6 | # Prints out version details of the various installed utilities. This is 7 | # useful for debugging agent failures. 8 | ################################################################################ 9 | 10 | set -e 11 | 12 | # Dump settings 13 | uname -a 14 | 15 | if ! test -z "${DOXYGEN_AGENT}"; then 16 | doxygen --version 17 | else 18 | for cmd in ${CXX_COMPILER} python3 ninja cmake conan; do 19 | echo "$cmd --version" 20 | $cmd --version 21 | done 22 | fi 23 | -------------------------------------------------------------------------------- /.conan/test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from conans import ConanFile, CMake 4 | 5 | class ScopeConanTest(ConanFile): 6 | settings = "os", "compiler", "arch", "build_type" 7 | generators = "cmake" 8 | 9 | def build(self): 10 | cmake = CMake(self) 11 | cmake.configure() 12 | cmake.build() 13 | 14 | def test(self): 15 | if str(self.settings.os) in ["Linux", "Macos"]: 16 | self.run("Scope.ConanTestPackage") 17 | elif str(self.settings.os) in ["Windows"]: 18 | self.run("Scope.ConanTestPackage.exe") 19 | else: 20 | self.output.warn("Skipping unit test execution due to cross compiling for {}".format(self.settings.os)) 21 | -------------------------------------------------------------------------------- /doc/legal.md: -------------------------------------------------------------------------------- 1 | # Legal 2 | 3 | 4 | 5 | **{Scope}** is licensed under the [Boost 1.0 License](../LICENSE), 6 | which is an open-source initiative approved license. 7 | 8 | Under this license, you may use **{Scope}** for any purpose you wish 9 | _WITHOUT ANY WARRANTY_, and modify it as you see fit. 10 | 11 | If it is used in a binary product, **no attribution is necessary**. 12 | If it is delivered in whole or in part as part of a source project, 13 | all that is required is that the legal copyright notice remains in 14 | tact. 15 | 16 | For more information, please refer to 17 | [Boost's license information page](https://www.boost.org/users/license.html) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | ### Checklist 8 | 9 | - [ ] I did not find a duplicate of this feature request in the Github Issues section. 10 | 11 | ---------- 12 | 13 | ### Description 14 | 27 | -------------------------------------------------------------------------------- /.travis/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ################################################################################ 3 | # Travis-CI : install 4 | # ------------------- 5 | # 6 | # Installs the required tools for the CI build 7 | ################################################################################ 8 | 9 | set -e 10 | 11 | # Nothing to install when we do a doxygen build 12 | if ! test -z "${DOXYGEN_AGENT}"; then 13 | exit 0 14 | fi 15 | 16 | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 17 | sudo python3 -m pip install --upgrade --force setuptools 18 | sudo python3 -m pip install --upgrade --force pip 19 | python3 -m pip install --user conan==1.11.1 20 | elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 21 | brew install python3 ninja cmake 22 | python3 -m pip install conan==1.11.1 23 | else 24 | echo >&2 "Unknown TRAVIS_OS_NAME: '${TRAVIS_OS_NAME}'" 25 | exit 1 26 | fi -------------------------------------------------------------------------------- /.travis/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ################################################################################ 3 | # Travis-CI : build 4 | # ----------------- 5 | # 6 | # Generates a CMake project, builds it, and tests it 7 | ################################################################################ 8 | 9 | set -e 10 | 11 | if ! test -z "${CXX_COMPILER}"; then 12 | export CXX=${CXX_COMPILER} 13 | fi 14 | 15 | build_directory="build" 16 | 17 | mkdir -p "${build_directory}" 18 | cd "${build_directory}" 19 | conan install .. 20 | 21 | cmake .. \ 22 | -G"Ninja" \ 23 | -DSCOPE_ENABLE_UNIT_TESTS=On \ 24 | ${CMAKE_OPTIONS} 25 | 26 | cmake --build . --config Release 27 | 28 | # TODO(bitwizeshift): 29 | # Optimize CI times by syncing the built binaries to a back-end, and running 30 | # tests as a distinct, separate step. This will allow for a quick-fail if 31 | # the code fails to compile. 32 | cmake --build . --target test 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | ### Checklist 8 | 9 | - [ ] I did not find a duplicate of this bug in the Github Issues section. 10 | 11 | ---------- 12 | 13 | ### Description 14 | 17 | 18 | 19 | ### Steps to reproduce 20 | 26 | 27 | #### Expected Behavior 28 | 31 | 32 | #### Actual Behavior 33 | 36 | 37 | ### Extra information 38 | 41 | * Library version: **X.Y.Z** 42 | * Operating System: **** 43 | * Compiler: **** 44 | -------------------------------------------------------------------------------- /.travis/deploy-conan.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ################################################################################ 3 | # Travis-CI : deploy-conan 4 | # ------------------------ 5 | # 6 | # Builds and deploys a conan project 7 | # 8 | # Environment: 9 | # BINTRAY_REMOTE_URL : The URL to push the deployed packages to 10 | # BINTRAY_USERNAME : The username of the bintray user that pushes packages 11 | # BINTRAY_API_KEY : The API key that corresponds to the bintray user 12 | ################################################################################ 13 | 14 | set -e 15 | 16 | test_path="$(pwd)/.conan/test_package" 17 | conanfile_path="$(pwd)" 18 | 19 | user="Scope" 20 | version=$(conan inspect "${conanfile_path}" --attribute version | sed 's@version: @@g') 21 | full_package="${user}/${version}@scope/stable" 22 | 23 | # Create and test the package 24 | conan create . "scope/stable" 25 | conan test "${test_path}" "${full_package}" 26 | 27 | # Set up the user information for uploading 28 | conan remote add scope ${BINTRAY_REMOTE_URL} 29 | conan user -p ${BINTRAY_API_KEY} -r scope ${BINTRAY_USERNAME} 30 | conan upload "${full_package}" -r scope --all -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Appveyor-CI 3 | # ----------- 4 | # 5 | ################################################################################ 6 | 7 | # Setup 8 | ################################################################################ 9 | 10 | version: "{build}" 11 | 12 | branches: 13 | except: 14 | - prototype/.* 15 | - gh-pages 16 | 17 | # Agents 18 | ################################################################################ 19 | 20 | os: 21 | - Visual Studio 2015 22 | - Visual Studio 2017 23 | 24 | # Jobs 25 | ################################################################################ 26 | 27 | platform: 28 | - Win32 29 | - x64 30 | 31 | configuration: 32 | - Debug 33 | - Release 34 | 35 | # Stages 36 | ################################################################################ 37 | 38 | install: 39 | - cmd: .\.appveyor\install.cmd 40 | 41 | before_build: 42 | - set CXXFLAGS="/permissive- /std:c++latest" 43 | - cmd: .\.appveyor\before-build.cmd 44 | 45 | build: 46 | project: build\Scope.sln 47 | parallel: true 48 | verbosity: normal 49 | 50 | test_script: 51 | - cmd: .\.appveyor\test.cmd -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Checklist 2 | 5 | - [ ] I have read the [CONTRIBUTING](CONTRIBUTING.md) guidelines 6 | - [ ] The coding style is consistent with the rest of the library 7 | - [ ] My branch's history is clean and coherent. This could be done through 8 | at least one of the following practices: 9 | * Rebasing my branch off of the branch being merged to 10 | * Squashing commits to create a more cohesive history 11 | - [ ] I have included unit-tests for my code 12 | 13 | ---------- 14 | 15 | ### Description 16 | 17 | 22 | 23 | ## GitHub Issues 24 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from conans import ConanFile, CMake 4 | 5 | class ScopeConan(ConanFile): 6 | 7 | # Package Info 8 | name = "Scope" 9 | version = "1.0.0" 10 | description = "An RAII scope-guard library" 11 | url = "https://github.com/bitwizeshift/Scope" 12 | author = "Matthew Rodusek " 13 | license = "BSL-1.0" 14 | 15 | # Sources 16 | exports = ( "LICENSE" ) 17 | exports_sources = ( "CMakeLists.txt", 18 | "cmake/*", 19 | "include/*", 20 | "LICENSE" ) 21 | 22 | # Settings 23 | options = {} 24 | default_options = {} 25 | generators = "cmake" 26 | 27 | # Dependencies 28 | build_requires = ("Catch2/2.7.1@catchorg/stable") 29 | 30 | 31 | def configure_cmake(self): 32 | cmake = CMake(self) 33 | 34 | # Features 35 | cmake.definitions["SCOPE_ENABLE_UNIT_TESTS"] = "OFF" 36 | 37 | cmake.configure() 38 | return cmake 39 | 40 | 41 | def build(self): 42 | cmake = self.configure_cmake() 43 | cmake.build() 44 | # cmake.test() 45 | 46 | 47 | def package(self): 48 | cmake = self.configure_cmake() 49 | cmake.install() 50 | 51 | self.copy(pattern="LICENSE", dst="licenses") 52 | 53 | 54 | def package_id(self): 55 | self.info.header_only() 56 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Catch2 REQUIRED) 2 | 3 | ############################################################################## 4 | # Target 5 | ############################################################################## 6 | 7 | set(source_files 8 | src/main.cpp 9 | src/scope/scope_exit.test.cpp 10 | src/scope/scope_fail.test.cpp 11 | src/scope/scope_success.test.cpp 12 | src/scope/unique_resource.test.cpp 13 | ) 14 | 15 | add_executable(${PROJECT_NAME}Test 16 | ${source_files} 17 | ) 18 | add_executable(${PROJECT_NAME}::Test ALIAS ${PROJECT_NAME}Test) 19 | 20 | if ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR 21 | "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) 22 | target_compile_options(${PROJECT_NAME}Test 23 | PRIVATE -Wall -Wextra -Werror -pedantic 24 | ) 25 | elseif( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" ) 26 | # TODO: Determine MSVC necessary compiler flags 27 | endif () 28 | 29 | set_target_properties(${PROJECT_NAME}Test 30 | PROPERTIES 31 | CXX_STANDARD_REQUIRED ON 32 | CXX_EXTENSIONS OFF 33 | ) 34 | 35 | target_link_libraries(${PROJECT_NAME}Test 36 | PRIVATE Catch2::Catch2 37 | PRIVATE ${PROJECT_NAME}::${PROJECT_NAME} 38 | ) 39 | 40 | ############################################################################## 41 | # CTest 42 | ############################################################################## 43 | 44 | include(Catch) 45 | catch_discover_tests(${PROJECT_NAME}Test 46 | WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" 47 | ) -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Did you find a bug? 4 | 5 | * Ensure the bug was not already reported by searching on Github under 6 | Issues. 7 | * If you're unable to find an open issue addressing the problem, open a new 8 | one. Be sure to include a title and clear description, as much relevant 9 | information as possible, and a code sample or an executable test case 10 | demonstrating the expected behavior that is not occurring. 11 | * If possible, use the relevant bug report template to create the issue. 12 | 13 | ## Did you fix a bug? 14 | 15 | * Open a new Github pull-request with the patch. 16 | * Ensure the PR description clearly describes the problem and solution. 17 | Include the relevant issue number if applicable. 18 | 19 | ## Do you intend to add a new feature, or change an existing one? 20 | 21 | * Open an issue on Github to get a discussion going first before 22 | proceeding. 23 | * Open individual issues for each suggested feature, and individual 24 | pull-requests for each one. 25 | * New features being introduce should include unit tests and 26 | documentation where necessary. 27 | 28 | ## Are you opening a pull-request? 29 | 30 | * The pull request should have a useful title 31 | * Please follow the existing conventions of the library for new contributions 32 | * Keep descriptions short and simple, but descriptive. 33 | * Start the description with a capital and end with a full stop/period. 34 | * Check your spelling and grammar. 35 | * Make sure your text editor is set to remove trailing whitespace. 36 | 37 | ## Do you have any questions about the library? 38 | 39 | * Open a Github issue with a title indicating the question. 40 | -------------------------------------------------------------------------------- /.travis/deploy-doxygen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ################################################################################ 3 | # Travis-CI : deploy-doxygen 4 | # -------------------------- 5 | # 6 | # Description: 7 | # Generates doxygen output, and pushes it to the gh-pages branch of the Scope 8 | # repository 9 | # 10 | # Requirements: 11 | # doxygen installed 12 | # graphviz installed 13 | # conan installed 14 | # 15 | # Environment: 16 | # GITHUB_REPO_REF : Reference to the github repo 17 | # GITHUB_TOKEN : Access token for github url 18 | # TRAVIS_BUILD_NUMBER : The build number that generates the documentation 19 | # TRAVIS_COMMIT : The commit hash that generates the documentatin 20 | ################################################################################ 21 | 22 | set -e 23 | 24 | if [[ $# -gt 1 ]]; then 25 | echo >&2 "Expected at most 1 argument. Received $#." 26 | echo >&2 "Usage: $0 [latest]" 27 | exit 1 28 | fi 29 | 30 | version="latest" 31 | if [[ $# -eq 0 ]]; then 32 | version="v$(conan inspect . --attribute version | sed 's@version: @@g')" 33 | elif [[ $1 != "latest" ]]; then 34 | echo >&2 "Expected at most 1 argument. Received $#." 35 | echo >&2 "Usage: $0 [latest]" 36 | exit 1 37 | fi 38 | 39 | # Generate documentation 40 | doxygen_output_path="$(pwd)/build/doc" 41 | mkdir -p "${doxygen_output_path}" 42 | doxygen "$(pwd)/.codedocs" 43 | 44 | if [[ ! -d "${doxygen_output_path}/html" ]] || [[ ! -f "${doxygen_output_path}/html/index.html" ]]; then 45 | echo 'Error: No documentation (html) files have been found!' >&2 46 | exit 1 47 | fi 48 | 49 | dist_path="$(pwd)/dist" 50 | api_doc_path="${dist_path}/api/${version}" 51 | 52 | # Clone a git repo for doxygen 53 | git clone --single-branch -b gh-pages "https://${GITHUB_REPO_REF}" "${dist_path}" 54 | git config --global push.default simple 55 | 56 | # Add a .nojekyll file 57 | touch "dist/.nojekyll" 58 | 59 | # Exchange the old api content for the new content 60 | rm -rf "${api_doc_path}" 61 | mkdir -p "${api_doc_path}" 62 | mv ${doxygen_output_path}/html/* "${api_doc_path}" 63 | 64 | # Add everything and upload 65 | ( 66 | cd "${dist_path}" 67 | git add --all 68 | git commit \ 69 | -m "Deploy codedocs to Github Pages" \ 70 | -m "Documentation updated by build ${TRAVIS_BUILD_NUMBER}." \ 71 | -m "Commit: '${TRAVIS_COMMIT}'" \ 72 | --author "Deployment Bot " \ 73 | --no-gpg-sign 74 | git push \ 75 | --force "https://${GITHUB_TOKEN}@${GITHUB_REPO_REF}" gh-pages \ 76 | > /dev/null 2>&1 77 | ) -------------------------------------------------------------------------------- /doc/installing.md: -------------------------------------------------------------------------------- 1 | # Installing 2 | 3 | Since **{Scope}** is a single-header, header-only library -- there are 4 | various possible options for how this could be installed and used in 5 | any project. 6 | 7 | The short version is that, in order to use this, you must at 8 | least have a `-I` to the `include` directory of this project, or 9 | directly to the directory `scope.hpp` is installed in. 10 | 11 | You can easily copy the `scope.hpp` file wherever you would like into 12 | your project, `git submodule` this repository in, install it with your 13 | favorite package manager, etc. 14 | 15 | However, if you are using either [`conan`](#conan) or [`CMake`](#cmake) 16 | (or both), you can also follow the additional instructions below: 17 | 18 | ## Conan 19 | 20 | This project provides a `conanfile.py` and public hosting on 21 | [bintray](https://bintray.com/bitwizeshift/Scope) for packages. 22 | 23 | Just follow the instructions in the bintray link for setting up this 24 | source as a conan repository, and add 25 | `Scope/@scope/stable` as a dependency in your 26 | `conanfile.py`, and you can install this into your project. 27 | 28 | ## CMake 29 | 30 | This project is written with idiomatic & "modern" `CMake` (3.4+) and 31 | provides the target `Scope::Scope` which can be used as a dependency 32 | in your cmake build. 33 | 34 | You can either [add this project via `add_subdirectory`](#via-subdirectory) 35 | or [pull this project in with a `find_package`](#via-installation) 36 | call after installing it. 37 | 38 | ### Via subdirectory 39 | 40 | If you have added this repo as a `git submodule`, or as a subtree, 41 | or just straight-up copied directly into your repo, you can add this 42 | project via `add_subdirectory` call before your target so that you can 43 | use the `Scope::Scope` target. 44 | 45 | For example: 46 | 47 | ```cmake 48 | # Add the 'Scope' repo sometime before you depend on the target 49 | add_subdirectory("external/Scope") 50 | 51 | # ... 52 | 53 | add_library(MyLibrary ...) 54 | target_link_libraries(MyLibrary 55 | PRIVATE Scope::Scope # 'PRIVATE" assuming private dependency... 56 | ) 57 | ``` 58 | 59 | And in your implementation of `MyLibrary`, you can easily include 60 | the project with `#include "scope/scope.hpp"`! 61 | 62 | ### Via installation 63 | 64 | You can also create an installation package of **{Scope}** and 65 | share with other users, or just use locally on your system. 66 | 67 | Then, to consume this package you just need to find it with 68 | `find_package`. For example: 69 | 70 | ```cmake 71 | find_package(Scope REQUIRED) 72 | 73 | # ... 74 | 75 | add_library(MyLibrary ...) 76 | target_link_libraries(MyLibrary 77 | PRIVATE Scope::Scope # 'PRIVATE" assuming private dependency... 78 | ) 79 | ``` 80 | 81 | And in your implementation of `MyLibrary`, you can easily include 82 | the project with `#include "scope/scope.hpp"`! -------------------------------------------------------------------------------- /test/src/scope/scope_exit.test.cpp: -------------------------------------------------------------------------------- 1 | #include "scope/scope.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #if __cplusplus >= 201703L 7 | TEST_CASE("scope_exit::scope_exit(Fn&&)", "[ctor]") 8 | { 9 | auto called = false; 10 | 11 | SECTION("Scope exit success") 12 | { 13 | SECTION("Release not called") 14 | { 15 | { 16 | auto scope = ::scope::scope_exit([&called] { 17 | called = true; 18 | }); 19 | (void) scope; 20 | } 21 | REQUIRE(called); 22 | } 23 | 24 | SECTION("Release called") 25 | { 26 | { 27 | auto scope = ::scope::scope_exit([&called] { 28 | called = true; 29 | }); 30 | scope.release(); 31 | } 32 | REQUIRE(!called); 33 | } 34 | } 35 | 36 | SECTION("Scope exit failure") 37 | { 38 | SECTION("Release not called") 39 | { 40 | try { 41 | auto scope = ::scope::scope_exit([&called] { 42 | called = true; 43 | }); 44 | (void) scope; 45 | throw std::exception{}; 46 | } catch (...) {} 47 | REQUIRE(called); 48 | } 49 | 50 | SECTION("Release called") 51 | { 52 | try { 53 | auto scope = ::scope::scope_exit([&called] { 54 | called = true; 55 | }); 56 | scope.release(); 57 | throw std::exception{}; 58 | } catch (...) {} 59 | REQUIRE(!called); 60 | } 61 | } 62 | } 63 | #endif 64 | 65 | TEST_CASE("make_scope_exit(Fn&&)", "utilities") 66 | { 67 | auto called = false; 68 | 69 | SECTION("Scope exit success") 70 | { 71 | SECTION("Release not called") 72 | { 73 | { 74 | auto scope = ::scope::make_scope_exit([&called] { 75 | called = true; 76 | }); 77 | (void) scope; 78 | } 79 | REQUIRE(called); 80 | } 81 | 82 | SECTION("Release called") 83 | { 84 | { 85 | auto scope = ::scope::make_scope_exit([&called] { 86 | called = true; 87 | }); 88 | scope.release(); 89 | } 90 | REQUIRE(!called); 91 | } 92 | } 93 | 94 | SECTION("Scope exit failure") 95 | { 96 | SECTION("Release not called") 97 | { 98 | try { 99 | auto scope = ::scope::make_scope_exit([&called] { 100 | called = true; 101 | }); 102 | (void) scope; 103 | throw std::exception{}; 104 | } catch (...) {} 105 | REQUIRE(called); 106 | } 107 | 108 | SECTION("Release called") 109 | { 110 | try { 111 | auto scope = ::scope::make_scope_exit([&called] { 112 | called = true; 113 | }); 114 | scope.release(); 115 | throw std::exception{}; 116 | } catch (...) {} 117 | REQUIRE(!called); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test/src/scope/scope_fail.test.cpp: -------------------------------------------------------------------------------- 1 | #include "scope/scope.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #if __cplusplus >= 201703L 7 | TEST_CASE("scope_fail::scope_fail(Fn&&)", "[ctor]") 8 | { 9 | auto called = false; 10 | 11 | SECTION("Scope exit success") 12 | { 13 | SECTION("Release not called") 14 | { 15 | { 16 | auto scope = ::scope::scope_fail([&called] { 17 | called = true; 18 | }); 19 | (void) scope; 20 | } 21 | REQUIRE(!called); 22 | } 23 | 24 | SECTION("Release called") 25 | { 26 | { 27 | auto scope = ::scope::scope_fail([&called] { 28 | called = true; 29 | }); 30 | scope.release(); 31 | } 32 | REQUIRE(!called); 33 | } 34 | } 35 | 36 | SECTION("Scope exit failure") 37 | { 38 | SECTION("Release not called") 39 | { 40 | try { 41 | auto scope = ::scope::scope_fail([&called] { 42 | called = true; 43 | }); 44 | (void) scope; 45 | throw std::exception{}; 46 | } catch (...) {} 47 | REQUIRE(called); 48 | } 49 | 50 | SECTION("Release called") 51 | { 52 | try { 53 | auto scope = ::scope::scope_fail([&called] { 54 | called = true; 55 | }); 56 | scope.release(); 57 | throw std::exception{}; 58 | } catch (...) {} 59 | REQUIRE(!called); 60 | } 61 | } 62 | } 63 | #endif 64 | 65 | TEST_CASE("make_scope_fail(Fn&&)", "utilities") 66 | { 67 | auto called = false; 68 | 69 | SECTION("Scope exit success") 70 | { 71 | SECTION("Release not called") 72 | { 73 | { 74 | auto scope = ::scope::make_scope_fail([&called] { 75 | called = true; 76 | }); 77 | (void) scope; 78 | } 79 | REQUIRE(!called); 80 | } 81 | 82 | SECTION("Release called") 83 | { 84 | { 85 | auto scope = ::scope::make_scope_fail([&called] { 86 | called = true; 87 | }); 88 | scope.release(); 89 | } 90 | REQUIRE(!called); 91 | } 92 | } 93 | 94 | SECTION("Scope exit failure") 95 | { 96 | SECTION("Release not called") 97 | { 98 | try { 99 | auto scope = ::scope::make_scope_fail([&called] { 100 | called = true; 101 | }); 102 | (void) scope; 103 | throw std::exception{}; 104 | } catch (...) {} 105 | REQUIRE(called); 106 | } 107 | 108 | SECTION("Release called") 109 | { 110 | try { 111 | auto scope = ::scope::make_scope_fail([&called] { 112 | called = true; 113 | }); 114 | scope.release(); 115 | throw std::exception{}; 116 | } catch (...) {} 117 | REQUIRE(!called); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test/src/scope/scope_success.test.cpp: -------------------------------------------------------------------------------- 1 | #include "scope/scope.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #if __cplusplus >= 201703L 7 | TEST_CASE("scope_success::scope_success(Fn&&)", "[ctor]") 8 | { 9 | auto called = false; 10 | 11 | SECTION("Scope exit success") 12 | { 13 | SECTION("Release not called") 14 | { 15 | { 16 | auto scope = ::scope::scope_success([&called] { 17 | called = true; 18 | }); 19 | (void) scope; 20 | } 21 | REQUIRE(called); 22 | } 23 | 24 | SECTION("Release called") 25 | { 26 | { 27 | auto scope = ::scope::scope_success([&called] { 28 | called = true; 29 | }); 30 | scope.release(); 31 | } 32 | REQUIRE(!called); 33 | } 34 | } 35 | 36 | SECTION("Scope exit failure") 37 | { 38 | SECTION("Release not called") 39 | { 40 | try { 41 | auto scope = ::scope::scope_success([&called] { 42 | called = true; 43 | }); 44 | (void) scope; 45 | throw std::exception{}; 46 | } catch (...) {} 47 | REQUIRE(!called); 48 | } 49 | 50 | SECTION("Release called") 51 | { 52 | try { 53 | auto scope = ::scope::scope_success([&called] { 54 | called = true; 55 | }); 56 | scope.release(); 57 | throw std::exception{}; 58 | } catch (...) {} 59 | REQUIRE(!called); 60 | } 61 | } 62 | } 63 | #endif 64 | 65 | TEST_CASE("make_scope_success(Fn&&)", "utilities") 66 | { 67 | auto called = false; 68 | 69 | SECTION("Scope exit success") 70 | { 71 | SECTION("Release not called") 72 | { 73 | { 74 | auto scope = ::scope::make_scope_success([&called] { 75 | called = true; 76 | }); 77 | (void) scope; 78 | } 79 | REQUIRE(called); 80 | } 81 | 82 | SECTION("Release called") 83 | { 84 | { 85 | auto scope = ::scope::make_scope_success([&called] { 86 | called = true; 87 | }); 88 | scope.release(); 89 | } 90 | REQUIRE(!called); 91 | } 92 | } 93 | 94 | SECTION("Scope exit failure") 95 | { 96 | SECTION("Release not called") 97 | { 98 | try { 99 | auto scope = ::scope::make_scope_success([&called] { 100 | called = true; 101 | }); 102 | (void) scope; 103 | throw std::exception{}; 104 | } catch (...) {} 105 | REQUIRE(!called); 106 | } 107 | 108 | SECTION("Release called") 109 | { 110 | try { 111 | auto scope = ::scope::make_scope_success([&called] { 112 | called = true; 113 | }); 114 | scope.release(); 115 | throw std::exception{}; 116 | } catch (...) {} 117 | REQUIRE(!called); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4) 2 | 3 | if (PROJECT_NAME) 4 | set(SCOPE_IS_SUBPROJECT TRUE) 5 | endif () 6 | 7 | ############################################################################## 8 | # Project Options 9 | ############################################################################## 10 | 11 | option(SCOPE_ENABLE_UNIT_TESTS "Compile and run the unit tests for this library" OFF) 12 | 13 | ############################################################################## 14 | # Project 15 | ############################################################################## 16 | 17 | if (NOT CMAKE_TESTING_ENABLED AND SCOPE_ENABLE_UNIT_TESTS) 18 | enable_testing() 19 | endif () 20 | 21 | project(Scope 22 | VERSION "1.0.0" 23 | LANGUAGES CXX 24 | ) 25 | 26 | set(SCOPE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR} CACHE INTERNAL "Major version of the Scope library") 27 | set(SCOPE_VERSION_MINOR ${PROJECT_VERSION_MINOR} CACHE INTERNAL "Minor version of the Scope library") 28 | set(SCOPE_VERSION_PATCH ${PROJECT_VERSION_PATCH} CACHE INTERNAL "Patch version of the Scope library") 29 | set(SCOPE_VERSION ${PROJECT_VERSION} CACHE INTERNAL "Version of Scope library") 30 | 31 | # If using conan, only set the find paths. This project is trying to be as 32 | # idiomatic for CMake as possible, and doesn't want to rely on deep hooks from 33 | # a 3rd-party package manager. 34 | if (EXISTS "${CMAKE_CURRENT_BINARY_DIR}/conanbuildinfo.cmake") 35 | include("${CMAKE_CURRENT_BINARY_DIR}/conanbuildinfo.cmake") 36 | conan_set_find_paths() 37 | endif () 38 | 39 | add_library(${PROJECT_NAME} INTERFACE) 40 | add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 41 | 42 | target_include_directories(${PROJECT_NAME} 43 | INTERFACE $ 44 | INTERFACE $ 45 | ) 46 | 47 | target_compile_features(${PROJECT_NAME} 48 | INTERFACE 49 | cxx_alias_templates 50 | cxx_attributes 51 | cxx_auto_type 52 | cxx_defaulted_functions 53 | cxx_defaulted_move_initializers 54 | cxx_delegating_constructors 55 | cxx_deleted_functions 56 | cxx_inheriting_constructors 57 | cxx_lambdas 58 | cxx_noexcept 59 | cxx_right_angle_brackets 60 | cxx_static_assert 61 | cxx_rvalue_references 62 | cxx_uniform_initialization 63 | ) 64 | 65 | if (SCOPE_ENABLE_UNIT_TESTS) 66 | add_subdirectory("test") 67 | endif () 68 | 69 | ############################################################################## 70 | # Installation 71 | ############################################################################## 72 | 73 | if (SCOPE_IS_SUBPROJECT) 74 | return() 75 | endif () 76 | 77 | include(GNUInstallDirs) 78 | include(CMakePackageConfigHelpers) 79 | 80 | set(SCOPE_CMAKE_CONFIG_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") 81 | 82 | # The generated *ConfigVersion is strongly tied to the architecture 83 | # it was generated on, and sets variables such as 'SIZEOF_VOID_P'. 84 | # Since this library is header-only, the host architecture does not 85 | # actually affect the target that consumes this project, so we fake 86 | # the variable to be empty, but reset it after. 87 | # 88 | # Otherwise a 64-bit creator would cause a 32-bit consumer to fail to 89 | # use this library, even though it's header-only. 90 | set(SCOPE_CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) 91 | set(CMAKE_SIZEOF_VOID_P "") 92 | write_basic_package_version_file( 93 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" 94 | VERSION "${PROJECT_VERSION}" 95 | COMPATIBILITY "SameMajorVersion" 96 | ) 97 | set(CMAKE_SIZEOF_VOID_P ${SCOPE_CMAKE_SIZEOF_VOID_P}) 98 | 99 | configure_package_config_file( 100 | "${CMAKE_CURRENT_LIST_DIR}/cmake/${PROJECT_NAME}Config.cmake.in" 101 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 102 | INSTALL_DESTINATION "${SCOPE_CMAKE_CONFIG_DESTINATION}" 103 | ) 104 | 105 | # Targets 106 | install( 107 | TARGETS "${PROJECT_NAME}" 108 | EXPORT "${PROJECT_NAME}Targets" 109 | DESTINATION "${CMAKE_INSTALL_LIBDIR}" 110 | ) 111 | install( 112 | EXPORT "${PROJECT_NAME}Targets" 113 | NAMESPACE "${PROJECT_NAME}::" 114 | DESTINATION "${SCOPE_CMAKE_CONFIG_DESTINATION}" 115 | ) 116 | install( 117 | FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" 118 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 119 | DESTINATION "${SCOPE_CMAKE_CONFIG_DESTINATION}" 120 | ) 121 | 122 | # Includes 123 | install( 124 | DIRECTORY "include/" 125 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 126 | ) 127 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Travis-CI 3 | # --------- 4 | # Pipeline: Build -> Test -> Validate -> Deploy 5 | # 6 | # Build: Builds the project 7 | # Test: Runs unit tests 8 | # Validate: Runs sanitizers 9 | # Deploy: Publishes documentation and binary 10 | ################################################################################ 11 | 12 | # Setup 13 | ################################################################################ 14 | 15 | # Enable C++ 16 | language: cpp 17 | cache: ccache 18 | 19 | dist: trusty 20 | sudo: required 21 | group: edge 22 | 23 | branches: 24 | except: 25 | - prototype/.* 26 | - gh-pages 27 | 28 | # Set python version to 3.6 29 | python: 30 | - "3.6" 31 | 32 | # Agents 33 | ################################################################################ 34 | 35 | linux_gcc49_agent: &linux_gcc49_agent 36 | os: linux 37 | compiler: gcc 38 | addons: 39 | apt: 40 | sources: ['ubuntu-toolchain-r-test'] 41 | packages: ['cmake', 'g++-4.9', 'python3', 'python3-pip', 'ninja-build'] 42 | update: true 43 | env: 44 | - CXX_COMPILER=g++-4.9 45 | 46 | linux_gcc5_agent: &linux_gcc5_agent 47 | os: linux 48 | compiler: gcc 49 | env: 50 | - CXX_COMPILER=g++-5 51 | addons: 52 | apt: 53 | sources: ['ubuntu-toolchain-r-test'] 54 | packages: ['cmake', 'g++-5', 'python3', 'python3-pip', 'ninja-build'] 55 | update: true 56 | 57 | linux_gcc6_agent: &linux_gcc6_agent 58 | os: linux 59 | compiler: gcc 60 | env: 61 | - CXX_COMPILER=g++-6 62 | addons: 63 | apt: 64 | sources: ['ubuntu-toolchain-r-test'] 65 | packages: ['cmake', 'g++-6', 'python3', 'python3-pip', 'ninja-build'] 66 | update: true 67 | 68 | linux_gcc7_agent: &linux_gcc7_agent 69 | os: linux 70 | compiler: gcc 71 | env: 72 | - CXX_COMPILER=g++-7 73 | addons: 74 | apt: 75 | sources: ['ubuntu-toolchain-r-test'] 76 | packages: ['cmake', 'g++-7', 'python3', 'python3-pip', 'ninja-build'] 77 | update: true 78 | 79 | linux_gcc8_agent: &linux_gcc8_agent 80 | os: linux 81 | compiler: gcc 82 | env: 83 | - CXX_COMPILER=g++-8 84 | addons: 85 | apt: 86 | sources: ['ubuntu-toolchain-r-test'] 87 | packages: ['cmake', 'g++-8', 'python3', 'python3-pip', 'ninja-build'] 88 | update: true 89 | 90 | ################################################################################ 91 | 92 | linux_clang35_agent: &linux_clang35_agent 93 | os: linux 94 | compiler: clang 95 | env: 96 | - CXX_COMPILER=clang++-3.5 97 | addons: 98 | apt: 99 | sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-8'] 100 | packages: ['cmake', 'clang-3.5', 'python3', 'python3-pip', 'ninja-build'] 101 | update: true 102 | 103 | linux_clang36_agent: &linux_clang36_agent 104 | os: linux 105 | compiler: clang 106 | env: 107 | - CXX_COMPILER=clang++-3.6 108 | addons: 109 | apt: 110 | sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-8'] 111 | packages: ['cmake', 'clang-3.6', 'python3', 'python3-pip', 'ninja-build'] 112 | update: true 113 | 114 | linux_clang38_agent: &linux_clang38_agent 115 | os: linux 116 | compiler: clang 117 | env: 118 | - CXX_COMPILER=clang++-3.8 119 | addons: 120 | apt: 121 | sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-8'] 122 | packages: ['cmake', 'clang-3.8', 'python3', 'python3-pip', 'ninja-build'] 123 | update: true 124 | 125 | linux_clang8_agent: &linux_clang8_agent 126 | os: linux 127 | compiler: clang 128 | env: 129 | - CXX_COMPILER=clang++-8 130 | addons: 131 | apt: 132 | sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-8'] 133 | packages: ['cmake', 'g++-8', 'clang-8', 'python3', 'python3-pip', 'ninja-build'] 134 | update: true 135 | 136 | ################################################################################ 137 | 138 | osx_build_agent: &osx_build_agent 139 | stage: build 140 | os: osx 141 | language: generic 142 | compiler: clang 143 | env: 144 | - CXX_COMPILER=clang++ 145 | addons: 146 | homebrew: 147 | packages: ['cmake', 'ninja', 'python3'] 148 | update: true 149 | script: ./.travis/build.sh 150 | 151 | ################################################################################ 152 | 153 | linux_doxygen_agent: &linux_doxygen_agent 154 | os: linux 155 | compiler: gcc 156 | env: 157 | - DOXYGEN_AGENT=true 158 | addons: 159 | apt: 160 | packages: ['doxygen'] 161 | 162 | # Stages 163 | ################################################################################ 164 | 165 | stages: 166 | - build 167 | - test 168 | - validate 169 | - deploy 170 | 171 | # Jobs 172 | ################################################################################ 173 | 174 | jobs: 175 | include: 176 | # Build 177 | ############################################################################ 178 | 179 | - <<: *linux_gcc49_agent 180 | stage: build 181 | name: "Build (linux-g++-4.9)" 182 | script: ./.travis/build.sh 183 | 184 | - <<: *linux_gcc5_agent 185 | stage: build 186 | name: "Build (linux-g++-5)" 187 | script: ./.travis/build.sh 188 | 189 | - <<: *linux_gcc6_agent 190 | stage: build 191 | name: "Build (linux-g++-6)" 192 | script: ./.travis/build.sh 193 | 194 | - <<: *linux_gcc7_agent 195 | stage: build 196 | name: "Build (linux-g++-7)" 197 | script: ./.travis/build.sh 198 | 199 | - <<: *linux_gcc8_agent 200 | stage: build 201 | name: "Build (linux-g++-8)" 202 | script: ./.travis/build.sh 203 | 204 | ########################################################################## 205 | 206 | - <<: *linux_clang35_agent 207 | stage: build 208 | name: "Build (linux-clang++-3.5)" 209 | script: ./.travis/build.sh 210 | 211 | - <<: *linux_clang36_agent 212 | stage: build 213 | name: "Build (linux-clang++-3.6)" 214 | script: ./.travis/build.sh 215 | 216 | - <<: *linux_clang8_agent 217 | stage: build 218 | name: "Build (linux-clang++-8.0)" 219 | script: ./.travis/build.sh 220 | 221 | ########################################################################## 222 | 223 | - <<: *osx_build_agent 224 | osx_image: xcode9 225 | name: "Build (macos-xcode9-clang)" 226 | 227 | - <<: *osx_build_agent 228 | osx_image: xcode9.1 229 | name: "Build (macos-xcode9.1-clang)" 230 | 231 | - <<: *osx_build_agent 232 | osx_image: xcode9.2 233 | name: "Build (macos-xcode9.2-clang)" 234 | 235 | - <<: *osx_build_agent 236 | osx_image: xcode9.3 237 | name: "Build (macos-xcode9.3-clang)" 238 | 239 | - <<: *osx_build_agent 240 | osx_image: xcode9.4 241 | name: "Build (macos-xcode9.44-clang)" 242 | 243 | - <<: *osx_build_agent 244 | osx_image: xcode10 245 | name: "Build (macos-xcode10-clang)" 246 | 247 | # Deploy 248 | ############################################################################ 249 | 250 | - <<: *linux_doxygen_agent 251 | stage: deploy 252 | name: "Deploy tag (doxygen)" 253 | if: (tag =~ ^v) AND (type = push) 254 | script: ./.travis/deploy-doxygen.sh 255 | 256 | - <<: *linux_doxygen_agent 257 | stage: deploy 258 | name: "Deploy latest (doxygen)" 259 | if: (branch = master) AND (type = push) 260 | script: ./.travis/deploy-doxygen.sh latest 261 | 262 | - <<: *linux_gcc8_agent 263 | stage: deploy 264 | name: "Deploy" 265 | if: (tag =~ ^v) AND (type = push) 266 | script: ./.travis/deploy-conan.sh 267 | 268 | install: 269 | - ./.travis/install.sh 270 | 271 | before_script: 272 | - ./.travis/before-script.sh -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Release](https://img.shields.io/github/release/bitwizeshift/Scope.svg)](https://github.com/bitwizeshift/Scope/releases/latest) 3 | [![License](https://img.shields.io/badge/License-BSL--1.0-blue.svg)](https://raw.githubusercontent.com/bitwizeshift/Scope/master/LICENSE) 4 | [![Github Issues](https://img.shields.io/github/issues/bitwizeshift/Scope.svg)](https://github.com/bitwizeshift/Scope/issues) 5 | 6 | # {Scope} 7 | 8 | **{Scope}** is a modern utility library for managing resources using RAII. 9 | This library features a permissive license ([Boost Software License](#license)), 10 | and is only a single header -- making it simple to drop into any project. 11 | This is compatible with any modern version of C++ provided it supports 12 | C++11 or greater. 13 | 14 | **Note:** This library is an implementation of the 15 | [p0052r6 proposal paper][1] that introduces the `` header and utilities 16 | to the C++ standard, but is written in C++11 with support for C++17 17 | type-deduced constructors -- rather than only supporting C++17 features. 18 | 19 | ## Table of Contents 20 | 21 | * [Features](#features) \ 22 | A summary of all existing features in **{Scope}** 23 | * [API Reference](https://bitwizeshift.github.io/Scope/api/latest/) \ 24 | For doxygen-generated API information 25 | * [Tutorials](doc/tutorial.md) \ 26 | For tutorials on how to use **{Scope}** 27 | * [How to install](doc/installing.md) \ 28 | For a quick guide on how to install/use this in other projects 29 | * [Legal](doc/legal.md) \ 30 | Information about how to attribute this project 31 | * [Contributing Guidelines](.github/CONTRIBUTING.md) \ 32 | Guidelines for how to contribute to this project 33 | 34 | ## Features 35 | 36 | - [x] Easily manage any C-style resources in an idiomatic way with 37 | `unique_resource` 38 | - [x] Handle writing clean-up code exactly once with the various scope guards; 39 | keep that code `DRY`! 40 | - [x] Works with C++11, and is fully functional in C++17 41 | - [x] Written with portable C++ code, and tested on various compilers and 42 | operating systems 43 | - [x] Only a single-header -- drop it in anywhere! 44 | - [x] Super permissive license with the [Boost License](#license) 45 | 46 | [1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0052r6.pdf 47 | 48 | ### Scope Guards 49 | 50 | Scope guards allow for deferring an action until the end of a given scope. 51 | This can be useful for various things: 52 | 53 | * Logging 54 | * Cleaning up resources in a function with multiple exit cases 55 | * Creating transactional support by 'rolling-back' changes on failure 56 | * etc 57 | 58 | Three different scope guards exist for handling different exit strategies: 59 | 60 | * **`scope_exit`**: Executes the callback every time 61 | * **`scope_success`**: Executes the callback only on successful scope exit 62 | (no exceptions thrown) 63 | * **`scope_fail`**: Executes the callback only on exceptional cases, when 64 | a scope is terminating due to a thrown exception. 65 | 66 | ### `unique_resource` 67 | 68 | The `unique_resource` type is a more general version of the C++ standard's 69 | `unique_ptr`, allowing for any underlying resource type to be managed with 70 | unique ownership. This utility is extremely helpful for managing resources 71 | distributed by old C-style interfaces. 72 | 73 | For example, this could be done to manage a POSIX file: 74 | 75 | ```c++ 76 | { 77 | auto file = ::scope::make_unique_resource( 78 | ::open(...), 79 | &::close 80 | ); 81 | // decltyp(file) is ::scope::unique_resource 135 | 136 | **{Scope}** is licensed under the 137 | [Boost Software License 1.0](https://www.boost.org/users/license.html): 138 | 139 | > Boost Software License - Version 1.0 - August 17th, 2003 140 | > 141 | > Permission is hereby granted, free of charge, to any person or organization 142 | > obtaining a copy of the software and accompanying documentation covered by 143 | > this license (the "Software") to use, reproduce, display, distribute, 144 | > execute, and transmit the Software, and to prepare derivative works of the 145 | > Software, and to permit third-parties to whom the Software is furnished to 146 | > do so, all subject to the following: 147 | > 148 | > The copyright notices in the Software and this entire statement, including 149 | > the above license grant, this restriction and the following disclaimer, 150 | > must be included in all copies of the Software, in whole or in part, and 151 | > all derivative works of the Software, unless such copies or derivative 152 | > works are solely in the form of machine-executable object code generated by 153 | > a source language processor. 154 | > 155 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 156 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 157 | > FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 158 | > SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 159 | > FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 160 | > ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 161 | > DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /doc/tutorial.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | **{Scope}** is really simple-to-use library with a relatively small 4 | API surface area. 5 | 6 | Overall, it only exposes 4 types: 7 | 8 | * `scope::scope_exit` 9 | * `scope::scope_success` 10 | * `scope::scope_fail` 11 | * `scope::unique_resource` 12 | 13 | each of which are described below. 14 | 15 | ## `scope::scope_exit` 16 | 17 | The `scope::scope_exit` class (and corresponding `scope::make_scope_exit` 18 | function) are used to to defer execution of a specified function until the 19 | end of a scope. The execution of this function is always guaranteed to occur, 20 | even if the scope ends early due to an exception. 21 | 22 | If you are using C++17 or above, you can use the template-deduction for 23 | `scope_exit`, but in C++14 or below you will require `scope::make_scope_exit` 24 | in order to achieve the same effect. 25 | 26 | This type is useful for a number of things: 27 | 28 | * Doing resource cleanup on several control-flow paths 29 | * Logging on exit 30 | * Computing runtime of a function that has several exit paths 31 | * etc 32 | 33 | **Note:** All scope guards MUST be assigned to a variable. The name 34 | of this variable does not matter, but without it there will be no 35 | RAII. 36 | 37 | ### Doing resource cleanup example 38 | 39 | Managing resources that cannot afford to be leaked in any condition, 40 | such as semaphores, are an excellent use-case for `scope_exit`: 41 | 42 | ```c++ 43 | { 44 | ::sem_lock(sem); 45 | 46 | // Always unlock semaphore at end of scope 47 | auto scope = ::scope::make_scope_exit([&]{ 48 | ::sem_post(sem); 49 | }); 50 | 51 | // ... 52 | 53 | // multiple return conditions that require unlocking 54 | if ( ... ) { 55 | return; 56 | } 57 | 58 | // ... 59 | 60 | return; 61 | } 62 | ``` 63 | 64 | ### Logging on exit example 65 | 66 | If you are in a logging-heavy system or function, which is commonly seen 67 | in highly asynchronous environments, it can be helpful to log the same message 68 | at multiple exit points. This is made easy with `scope_exit`: 69 | 70 | ```c++ 71 | { 72 | auto scope = ::scope::make_scope_exit([&]{ 73 | std::cout << "Leaving function now ..." << std::endl; 74 | }); 75 | 76 | 77 | // ... 78 | 79 | // multiple return conditions that require the same log 80 | if ( ... ) { 81 | return; 82 | } 83 | 84 | // ... 85 | 86 | } 87 | ``` 88 | 89 | ### Computing runtime of a function example 90 | 91 | ```c++ 92 | { 93 | const auto start_time = std::chrono::steady_clock::now(); 94 | 95 | auto scope = ::scope::make_scope_exit([&]{ 96 | const auto end_time = std::chrono::steady_clock::now(); 97 | const auto diff = end_time - start_time; 98 | do_something_with_calculated_time(diff); 99 | }); 100 | 101 | 102 | // ... 103 | 104 | // multiple return conditions that require the same timing 105 | if ( ... ) { 106 | return; 107 | } 108 | 109 | // ... 110 | } 111 | ``` 112 | 113 | ## `scope::scope_success` 114 | 115 | Similar to `scope_exit`, `scope_success` will defer execution until 116 | the end of the scope -- but instead it will only execute if the scope 117 | is being left due to a normal condition, and not due to an exception 118 | being thrown. 119 | 120 | This has many potential uses, such as: 121 | 122 | * Committing an action for atomicity (e.g. transactions) 123 | * Recording/notifying successful conditions 124 | * etc 125 | 126 | **Note:** All scope guards MUST be assigned to a variable. The name 127 | of this variable does not matter, but without it there will be no 128 | RAII. 129 | 130 | ### Commiting an action atomically example 131 | 132 | Sometimes you might have a complicated control flow that should 133 | only ever post a value in successful states, but not in failure 134 | states. This is where `scope_success` comes in: 135 | 136 | ```c++ 137 | { 138 | // copy the old state of things 139 | auto state = m_state.copy(); 140 | 141 | auto scope = ::scope::make_scope_success([&state, this]{ 142 | this->commit(state); 143 | }); 144 | 145 | // do some potentially throwing stuff... that 146 | // modifies 'state' 147 | } 148 | ``` 149 | 150 | ### Recording/notifying successful conditions example 151 | 152 | In some cases, you want to notify that a change has been made 153 | to your code, and this information should be propagated to all 154 | consumers. This could be done via logging, observers, signals, 155 | etc -- but the key point is that someone is listening to this change. 156 | 157 | This is where `scope_success` can be useful: 158 | 159 | ```c++ 160 | { 161 | auto scope = ::scope::make_scope_success([this]{ 162 | this->notify_observers(this->m_some_value); 163 | }); 164 | 165 | // try to change 'm_some_value', throws on error 166 | } 167 | ``` 168 | 169 | ## `scope::scope_fail` 170 | 171 | The `scope_fail` is effectively the inverse of `scope_success` in that 172 | it will always defer execution until the end of the scope, but will only 173 | execute if the scope was left due to an exception being in-flight. 174 | 175 | This can be extremely useful for: 176 | 177 | * Rolling back changes for atomic-transactions 178 | * Notifying failure conditions 179 | * etc 180 | 181 | **Note:** All scope guards MUST be assigned to a variable. The name 182 | of this variable does not matter, but without it there will be no 183 | RAII. 184 | 185 | ### Rolling back changes example 186 | 187 | This can be done in connection with `make_scope_success` to provide 188 | full atomic transactions, where `scope_fail` will roll back changes on 189 | failure, but `scope_success` will commit them! 190 | 191 | ```c++ 192 | { 193 | auto state = m_state.copy(); 194 | 195 | auto scope_fail = ::scope::make_scope_fail([&state, this]{ 196 | this->rollback(state); 197 | }); 198 | auto scope_success = ::scope::make_scope_success([&state, this]{ 199 | this->commit(state); 200 | }); 201 | 202 | // do some potentially throwing stuff... that 203 | 204 | // modifies 'state' 205 | } 206 | ``` 207 | 208 | ### Notifying failure conditions example 209 | 210 | It can always be useful to notify that a bad state has happened. 211 | This can be done through logging, observers, signals, etc -- but 212 | the key aspect is that something went bad, and someone else needs 213 | to know about it. This is where `scope_fail` comes in: 214 | 215 | ```c++ 216 | { 217 | auto scope = ::scope::make_scope_fail([this]{ 218 | this->notify_something_bad_happened(); 219 | }); 220 | 221 | // something that may throw on error 222 | } 223 | ``` 224 | 225 | ## `scope::unique_resource` 226 | 227 | `unique_resource` is a more generic version of `unique_ptr`. It has 228 | unique ownership semantics, but can hold any arbitrary type `T` rather 229 | than only pointers. This allows for interfacing directly with types that 230 | distribute resources that must be deleted other calls. This works extremely 231 | well with older C-style APIs that return handles and must be deleted on 232 | cleanup. 233 | 234 | `unique_resource` also provides two utility construction functions for deducing 235 | the types, and for ensuring that the constructed value is not a _bad_ value. 236 | For example, POSIX `::open` will return a file descriptor integer `-1` on failure, 237 | which should not be `::close`d. 238 | 239 | ### Posix File Example 240 | 241 | The simple example is interfacing with a Posix-style C API, such as the file API: 242 | 243 | ```c++ 244 | { 245 | auto file = ::scope::make_unique_resource_checked( 246 | ::open( ... ), // Creates the resource 247 | -1, // The 'invalid' value (ensures no deletion) 248 | &::close // function for deleting this 249 | ); 250 | 251 | // do stuff with 'file' 252 | 253 | } 254 | ``` 255 | 256 | ### SDL2 Example 257 | 258 | Realistically, any library that returns handls of any kind can be used by this 259 | utility. For example, if you are using the SDL2 library, this can help manage 260 | any pointers returned from their API calls: 261 | 262 | ```c++ 263 | { 264 | auto window = ::scope::make_unique_resource_checked( 265 | ::SDL_CreateWindow( ... ), 266 | nullptr, 267 | &::SDL_CloseWindow 268 | ); 269 | 270 | // do stuff with 'window' 271 | 272 | } 273 | ``` 274 | 275 | ### More complicated examples 276 | 277 | What if you have a more complicated example that isn't just a C API using a 278 | function pointer? In that scenario, the deleter can be a lambda or functor 279 | type that does some more complicated actions on callbacks. 280 | 281 | Let's use a resource system from an imaginary game-engine as an example. 282 | 283 | ```c++ 284 | { 285 | auto shader = ::scope::make_unique_resource_checked( 286 | shader_resource_manager.get_resource(...), 287 | ShaderResourceManager::BadHandle, 288 | [&shader_resource_manager](ShaderResourceManager::Handle& handle){ 289 | shader_resource_manager.dispose_resource(handle); 290 | } 291 | ); 292 | 293 | // do something with 'shader' 294 | } 295 | ``` 296 | 297 | Overall, this can help to reduce a lot of boilerplate that would otherwise 298 | come with creating handle types for complicated systems, since it can now 299 | be boiled down to two things: The resource value, and the system that 300 | disposes it. -------------------------------------------------------------------------------- /test/src/scope/unique_resource.test.cpp: -------------------------------------------------------------------------------- 1 | #include "scope/scope.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace { 7 | 8 | void example_deleter( int ){} 9 | 10 | template 11 | struct mock_resource { 12 | T value; 13 | }; 14 | 15 | template 16 | bool operator==(const mock_resource& lhs, const mock_resource& rhs) 17 | { 18 | return lhs.value == rhs.value; 19 | } 20 | template 21 | bool operator<(const mock_resource& lhs, const mock_resource& rhs) 22 | { 23 | return lhs.value < rhs.value; 24 | } 25 | 26 | // Maps type to number of times deleter has been called 27 | template 28 | using deleter_map = std::map,int>; 29 | 30 | template 31 | struct mock_deleter { 32 | ::deleter_map* map; 33 | void operator()(mock_resource& r) noexcept(false) 34 | { 35 | (*map)[r]++; 36 | } 37 | }; 38 | 39 | using example_deleter_type = decltype(&::example_deleter); 40 | } 41 | 42 | TEST_CASE("unique_resource::unique_resource(R&&, D&&)", "[ctor]") 43 | { 44 | SECTION("R and D are value types") 45 | { 46 | using resource_type = ::mock_resource; 47 | using deleter_type = ::mock_deleter; 48 | using result_type = ::scope::unique_resource; 49 | 50 | const auto value = 42; 51 | auto map = ::deleter_map{}; 52 | 53 | auto resource = resource_type{value}; 54 | auto deleter = deleter_type{&map}; 55 | 56 | auto r = result_type{resource, deleter}; 57 | 58 | SECTION("Contains copy of resource value") 59 | { 60 | REQUIRE(r.get().value == resource.value); 61 | } 62 | SECTION("Contains copy of deleter") 63 | { 64 | REQUIRE(r.get_deleter().map == deleter.map); 65 | } 66 | } 67 | 68 | SECTION("R is reference and D is value type") 69 | { 70 | using resource_type = ::mock_resource; 71 | using deleter_type = ::mock_deleter; 72 | using result_type = ::scope::unique_resource; 73 | 74 | const auto value = 42; 75 | auto map = ::deleter_map{}; 76 | auto resource = resource_type{value}; 77 | auto deleter = deleter_type{&map}; 78 | 79 | auto r = result_type{resource, deleter}; 80 | 81 | SECTION("Contains reference to resource") 82 | { 83 | const auto& reference = r.get(); 84 | REQUIRE(&reference.value == &resource.value); 85 | } 86 | SECTION("Contains copy of deleter") 87 | { 88 | REQUIRE(r.get_deleter().map == deleter.map); 89 | } 90 | } 91 | 92 | SECTION("R is value type and D is reference") 93 | { 94 | using resource_type = ::mock_resource; 95 | using deleter_type = ::mock_deleter; 96 | using result_type = ::scope::unique_resource; 97 | 98 | const auto value = 42; 99 | auto map = ::deleter_map{}; 100 | auto resource = resource_type{value}; 101 | auto deleter = deleter_type{&map}; 102 | 103 | auto r = result_type{resource, deleter}; 104 | 105 | SECTION("Contains copy of resource") 106 | { 107 | REQUIRE(r.get().value == resource.value); 108 | } 109 | SECTION("Contains reference to deleter") 110 | { 111 | REQUIRE(&r.get_deleter().map == &deleter.map); 112 | } 113 | } 114 | 115 | SECTION("R and D are reference types") 116 | { 117 | using resource_type = ::mock_resource; 118 | using deleter_type = ::mock_deleter; 119 | using result_type = ::scope::unique_resource; 120 | 121 | const auto value = 42; 122 | 123 | auto map = ::deleter_map{}; 124 | auto resource = resource_type{value}; 125 | auto deleter = deleter_type{&map}; 126 | 127 | auto r = result_type{resource, deleter}; 128 | 129 | SECTION("Contains reference to resource") 130 | { 131 | REQUIRE(&r.get().value == &resource.value); 132 | } 133 | SECTION("Contains reference to deleter") 134 | { 135 | REQUIRE(&r.get_deleter().map == &deleter.map); 136 | } 137 | } 138 | } 139 | 140 | TEST_CASE("unique_resource::reset()", "[modifiers]") 141 | { 142 | using resource_type = ::mock_resource; 143 | using deleter_type = ::mock_deleter; 144 | 145 | SECTION("Resource is reset without new value") 146 | { 147 | const auto value = 42; 148 | auto map = ::deleter_map{}; 149 | auto resource = resource_type{value}; 150 | auto deleter = deleter_type{&map}; 151 | 152 | { 153 | auto r = ::scope::make_unique_resource( 154 | resource, 155 | deleter 156 | ); 157 | 158 | r.reset(); // should call delete 159 | } 160 | 161 | SECTION("Calls delete once") 162 | { 163 | REQUIRE(map[resource] == 1); 164 | } 165 | SECTION("Calls delete on only the one resource") 166 | { 167 | REQUIRE(map.size() == 1u); 168 | } 169 | } 170 | } 171 | 172 | TEST_CASE("unique_resource::reset(RR&&)", "[modifiers]") 173 | { 174 | using resource_type = ::mock_resource; 175 | using deleter_type = ::mock_deleter; 176 | 177 | SECTION("Resource is reset with new value") 178 | { 179 | const auto value = 42; 180 | const auto new_value = value + 1; 181 | 182 | auto map = ::deleter_map{}; 183 | auto resource = resource_type{value}; 184 | auto new_resource = resource_type{new_value}; 185 | auto deleter = deleter_type{&map}; 186 | { 187 | auto r = ::scope::make_unique_resource( 188 | resource, 189 | deleter 190 | ); 191 | 192 | r.reset(new_resource); // should call delete 193 | } // should call delete again 194 | 195 | SECTION("Calls delete on old resource once") 196 | { 197 | REQUIRE(map[resource] == 1); 198 | } 199 | SECTION("Calls delete on new resource once") 200 | { 201 | REQUIRE(map[new_resource] == 1); 202 | } 203 | SECTION("Calls deleter on only the two resources") 204 | { 205 | REQUIRE(map.size() == 2u); 206 | } 207 | } 208 | } 209 | 210 | TEST_CASE("unique_resource::release()", "[modifiers]") 211 | { 212 | using resource_type = ::mock_resource; 213 | using deleter_type = ::mock_deleter; 214 | 215 | SECTION("Not released") 216 | { 217 | const auto value = 42; 218 | 219 | auto map = ::deleter_map{}; 220 | auto resource = resource_type{value}; 221 | auto deleter = deleter_type{&map}; 222 | 223 | { 224 | auto r = ::scope::make_unique_resource( 225 | resource, 226 | deleter 227 | ); 228 | (void) r; 229 | } // should call delete on scope exit 230 | 231 | SECTION("Calls deleter on resource") 232 | { 233 | REQUIRE(map[resource] == 1); 234 | } 235 | SECTION("Calls deleter only once") 236 | { 237 | REQUIRE(map.size() == 1u); 238 | } 239 | } 240 | 241 | SECTION("Released") 242 | { 243 | const auto value = 42; 244 | 245 | auto map = ::deleter_map{}; 246 | auto resource = resource_type{value}; 247 | auto deleter = deleter_type{&map}; 248 | 249 | { 250 | auto r = ::scope::make_unique_resource( 251 | resource, 252 | deleter 253 | ); 254 | r.release(); 255 | } // should not call delete on scope exit 256 | 257 | SECTION("Does not call deleter on any resource") 258 | { 259 | REQUIRE(map.empty()); 260 | } 261 | } 262 | } 263 | 264 | TEST_CASE("make_unique_resource(R&&, D&&)") 265 | { 266 | const auto value = 5; 267 | const auto deleter = &::example_deleter; 268 | 269 | auto resource = ::scope::make_unique_resource( 270 | value, 271 | deleter 272 | ); 273 | 274 | SECTION("Constructed resource contains value") 275 | { 276 | REQUIRE(resource.get() == value); 277 | } 278 | 279 | SECTION("Constructed resource contains deleter") 280 | { 281 | REQUIRE(resource.get_deleter() == deleter); 282 | } 283 | } 284 | 285 | TEST_CASE("make_unique_resource_checked(R&&, const S&, D&&)") 286 | { 287 | using resource_type = ::mock_resource; 288 | using deleter_type = ::mock_deleter; 289 | 290 | const auto invalid_value = -1; 291 | const auto invalid_resource = resource_type{invalid_value}; 292 | 293 | SECTION("Constructed with invalid value") 294 | { 295 | auto map = ::deleter_map{}; 296 | auto resource = invalid_resource; 297 | auto deleter = deleter_type{&map}; 298 | { 299 | 300 | auto r = ::scope::make_unique_resource_checked( 301 | resource, 302 | invalid_resource, 303 | deleter 304 | ); 305 | (void) r; 306 | } // should not call delete on scope exit 307 | 308 | SECTION("Destructor does not delete any resource") 309 | { 310 | REQUIRE(map.empty()); 311 | } 312 | } 313 | 314 | SECTION("Constructed with valid value") 315 | { 316 | const auto value = 42; 317 | auto map = ::deleter_map{}; 318 | auto resource = resource_type{value}; 319 | auto deleter = deleter_type{&map}; 320 | { 321 | 322 | auto r = ::scope::make_unique_resource_checked( 323 | resource, 324 | invalid_resource, 325 | deleter 326 | ); 327 | (void) r; 328 | } // should call delete on scope exit 329 | SECTION("Destructor calls delete on resource") 330 | { 331 | REQUIRE(map[resource] == 1); 332 | } 333 | SECTION("Delete only one resource once") 334 | { 335 | REQUIRE(map.size() == 1u); 336 | } 337 | } 338 | } -------------------------------------------------------------------------------- /include/scope/scope.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * \file scope.hpp 3 | * 4 | * This header contains the definition for scope utilities, as defined in the 5 | * p0052r6 proposal paper: 6 | * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0052r6.pdf 7 | * 8 | * This file is written in C++11, with compatible extensions for C++17 9 | * through the constructor deduction guides. 10 | ****************************************************************************/ 11 | 12 | // Copyright Matthew Rodusek 2019. 13 | // Distributed under the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | #ifndef SCOPE_SCOPE_HPP 17 | #define SCOPE_SCOPE_HPP 18 | 19 | #include // std::decay, std::is_* 20 | #include // std::reference_wrapper, std::ref 21 | #include // std::forward, std::move 22 | #include // std::uncaught_exception(s) 23 | #include // std::numeric_limits 24 | 25 | //! \def SCOPE_NODISCARD 26 | //! 27 | //! \brief A macro which expands into the attribute for warning on a 28 | //! discarded value. 29 | //! 30 | //! This is used within Scope to ensure that a user does not create a 31 | //! temporary scope guard instance without holding onto it for the 32 | //! remainder of the scope. 33 | #if __cplusplus >= 201703L 34 | # define SCOPE_NODISCARD [[nodiscard]] 35 | #elif defined(__GNUC__) || defined(__CLANG__) 36 | # define SCOPE_NODISCARD __attribute__ ((warn_unused_result)) 37 | #elif defined(_MSC_VER) && (_MSC_VER >= 1700) 38 | # define SCOPE_NODISCARD _Check_return_ 39 | #else 40 | # define SCOPE_NODISCARD 41 | #endif 42 | 43 | // The large #if/endif block below, and the definition of 44 | // scope::detail::uncaught_exception_count is adapted from boost: 45 | // https://beta.boost.org/doc/libs/develop/boost/core/uncaught_exceptions.hpp 46 | 47 | // Copyright Andrey Semashev 2018. 48 | // Distributed under the Boost Software License, Version 1.0. 49 | // (See accompanying file LICENSE_1_0.txt or copy at 50 | // http://www.boost.org/LICENSE_1_0.txt) 51 | 52 | #if (defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411) || \ 53 | defined(_MSC_VER) && _MSC_VER >= 1900 54 | # define SCOPE_HAS_UNCAUGHT_EXCEPTIONS 55 | #endif 56 | 57 | #if !defined(SCOPE_HAS_UNCAUGHT_EXCEPTIONS) 58 | 59 | // cxxabi.h availability macro 60 | # if defined(__has_include) 61 | # if __has_include() 62 | # define SCOPE_HAS_CXXABI_H 63 | # endif 64 | # elif defined(__GLIBCXX__) || defined(__GLIBCPP__) 65 | # define SCOPE_HAS_CXXABI_H 66 | # endif 67 | 68 | # if defined(SCOPE_HAS_CXXABI_H) 69 | // MinGW GCC 4.4 seem to not work the same way the newer GCC versions do. As 70 | // a result, __cxa_get_globals based implementation will always return 0. 71 | // Just disable it for now and fall back to std::uncaught_exception(). 72 | # if !(defined(__MINGW32__) && (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) < 405)) 73 | # include 74 | # include 75 | # define SCOPE_HAS_CXA_GET_GLOBALS 76 | 77 | // At least on MinGW and Linux, only GCC since 4.7 declares __cxa_get_globals() 78 | // in cxxabi.h. Older versions of GCC do not expose this function but it's 79 | // there. 80 | // On OpenBSD, it seems, the declaration is also missing. 81 | // Note that at least on FreeBSD 11, cxxabi.h declares __cxa_get_globals with 82 | // a different exception specification, so we can't declare the function 83 | // unconditionally. On Linux with clang and libc++ and on OS X, there is a 84 | // version of cxxabi.h from libc++abi that doesn't declare __cxa_get_globals, 85 | // but provides __cxa_uncaught_exceptions. 86 | // The function only appeared in version _LIBCPPABI_VERSION >= 1002 of the 87 | // library. Unfortunately, there are linking errors about undefined reference 88 | // to __cxa_uncaught_exceptions on Ubuntu Trusty and OS X, so we avoid using 89 | // it and forward-declare __cxa_get_globals instead. On QNX SDP 7.0 (QCC 5.4.0), 90 | // there are multiple cxxabi.h, one from glibcxx from gcc and another from 91 | // libc++abi from LLVM. Which one is included will be determined by the qcc 92 | // command line arguments (-V and/or -Y; 93 | // http://www.qnx.com/developers/docs/7.0.0/#com.qnx.doc.neutrino.utilities/topic/q/qcc.html 94 | // ). 95 | // The LLVM libc++abi is missing the declaration of __cxa_get_globals but it is 96 | // also patched by QNX developers to not define _LIBCPPABI_VERSION. Older QNX 97 | // SDP versions, up to and including 6.6, don't provide LLVM and libc++abi. 98 | // See https://github.com/boostorg/core/issues/59. 99 | # if !defined(__FreeBSD__) && \ 100 | ( \ 101 | (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) < 407) || \ 102 | defined(__OpenBSD__) || \ 103 | (defined(__QNXNTO__) && !defined(__GLIBCXX__) && !defined(__GLIBCPP__)) || \ 104 | defined(_LIBCPPABI_VERSION) \ 105 | ) 106 | namespace __cxxabiv1 { 107 | struct __cxa_eh_globals; 108 | # if defined(__OpenBSD__) 109 | extern "C" __cxa_eh_globals* __cxa_get_globals(); 110 | # else 111 | extern "C" __cxa_eh_globals* __cxa_get_globals() noexcept __attribute__((__const__)); 112 | # endif 113 | } // namespace __cxxabiv1 114 | # endif 115 | # endif // !(defined(__MINGW32__) && (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) < 405)) 116 | # endif // defined(SCOPE_HAS_CXXABI_H) 117 | 118 | # if defined(_MSC_VER) && _MSC_VER >= 1400 119 | # include 120 | # define SCOPE_HAS_GETPTD 121 | namespace scope { 122 | namespace detail { 123 | extern "C" void* _getptd(); 124 | } // namespace detail 125 | } // namespace scope 126 | # endif // defined(_MSC_VER) && _MSC_VER >= 1400 127 | #endif // !defined(SCOPE_HAS_UNCAUGHT_EXCEPTIONS) 128 | 129 | #if !defined(SCOPE_HAS_UNCAUGHT_EXCEPTIONS) && \ 130 | !defined(SCOPE_HAS_CXA_GET_GLOBALS) && \ 131 | !defined(SCOPE_HAS_GETPTD) 132 | //! This macro is defined when `uncaught_exceptions` is not guaranteed to 133 | //! return values greater than 1 if multiple exceptions are pending 134 | # define SCOPE_UNCAUGHT_EXCEPTIONS_EMULATED 135 | #endif 136 | 137 | namespace scope { 138 | 139 | //========================================================================== 140 | // implementation utilities 141 | //========================================================================== 142 | 143 | namespace detail { 144 | 145 | inline int uncaught_exception_count() 146 | { 147 | #if defined(SCOPE_HAS_UNCAUGHT_EXCEPTIONS) 148 | // C++17 implementation 149 | return std::uncaught_exceptions(); 150 | #elif defined(SCOPE_HAS_CXA_GET_GLOBALS) 151 | // Tested on {clang 3.2,GCC 3.5.6,GCC 4.1.2,GCC 4.4.6,GCC 4.4.7}x{x32,x64} 152 | using byte = unsigned char; 153 | auto count = int{}; 154 | const auto* ptr = reinterpret_cast(::abi::__cxa_get_globals()) + sizeof(void*); 155 | 156 | // __cxa_eh_globals::uncaughtExceptions, x32 offset - 0x4, x64 - 0x8 157 | std::memcpy(&count, ptr, sizeof(count)); 158 | return count; 159 | #elif defined(SCOPE_HAS_GETPTD) 160 | // MSVC specific. Tested on {MSVC2005SP1,MSVC2008SP1,MSVC2010SP1,MSVC2012}x{x32,x64}. 161 | using byte = unsigned char; 162 | auto count = int{}; 163 | 164 | const auto offset = (sizeof(void*) == 8u ? 0x100 : 0x90); 165 | const auto* ptr = static_cast(::scope::detail::_getptd()) + offset; 166 | 167 | // _tiddata::_ProcessingThrow, x32 offset - 0x90, x64 - 0x100 168 | std::memcpy(&count, ptr, sizeof(count)); 169 | return count; 170 | #else 171 | // Portable C++03 implementation. Does not allow to detect multiple 172 | // nested exceptions. 173 | 174 | // This is a buggy fallback since it will only work with 1 exception 175 | // in-flight, but we don't have any other options without exploiting 176 | // internal compiler features. 177 | return static_cast(std::uncaught_exception()); 178 | #endif 179 | } 180 | 181 | //======================================================================== 182 | // internal class : on_exit_policy 183 | //======================================================================== 184 | 185 | class on_exit_policy 186 | { 187 | public: 188 | on_exit_policy() noexcept 189 | : m_should_execute{true} 190 | { 191 | 192 | } 193 | on_exit_policy(on_exit_policy&&) = default; 194 | on_exit_policy(const on_exit_policy&) = default; 195 | on_exit_policy& operator=(on_exit_policy&&) = default; 196 | on_exit_policy& operator=(const on_exit_policy&) = default; 197 | 198 | void release() noexcept 199 | { 200 | m_should_execute = false; 201 | } 202 | 203 | bool should_execute() const noexcept 204 | { 205 | return m_should_execute; 206 | } 207 | 208 | private: 209 | 210 | bool m_should_execute; 211 | }; 212 | 213 | //======================================================================== 214 | // internal class : on_fail_policy 215 | //======================================================================== 216 | 217 | class on_fail_policy 218 | { 219 | public: 220 | on_fail_policy() noexcept 221 | : m_exception_count{uncaught_exception_count()} 222 | { 223 | 224 | } 225 | on_fail_policy(on_fail_policy&&) = default; 226 | on_fail_policy(const on_fail_policy&) = default; 227 | on_fail_policy& operator=(on_fail_policy&&) = default; 228 | on_fail_policy& operator=(const on_fail_policy&) = default; 229 | 230 | void release() noexcept 231 | { 232 | m_exception_count = std::numeric_limits::max(); 233 | } 234 | 235 | bool should_execute() const noexcept 236 | { 237 | return m_exception_count < uncaught_exception_count(); 238 | } 239 | 240 | private: 241 | 242 | int m_exception_count; 243 | }; 244 | 245 | //======================================================================== 246 | // internal class : on_success_policy 247 | //======================================================================== 248 | 249 | class on_success_policy 250 | { 251 | public: 252 | on_success_policy() noexcept 253 | : m_exception_count{uncaught_exception_count()} 254 | { 255 | 256 | } 257 | 258 | on_success_policy(on_success_policy&&) = default; 259 | on_success_policy(const on_success_policy&) = default; 260 | on_success_policy& operator=(on_success_policy&&) = default; 261 | on_success_policy& operator=(const on_success_policy&) = default; 262 | 263 | void release() noexcept 264 | { 265 | m_exception_count = -1; 266 | } 267 | 268 | bool should_execute() const noexcept 269 | { 270 | return m_exception_count == uncaught_exception_count(); 271 | } 272 | 273 | private: 274 | 275 | int m_exception_count; 276 | }; 277 | 278 | //======================================================================== 279 | // internal non-member functions 280 | //======================================================================== 281 | 282 | template 283 | std::conditional< 284 | std::is_nothrow_move_assignable::value, 285 | T&&, 286 | T const& 287 | > 288 | move_if_noexcept_assignable(T& x) noexcept 289 | { 290 | return std::move(x); 291 | } 292 | 293 | template 294 | const T& as_const(T& t) noexcept { return t; } 295 | 296 | //======================================================================== 297 | // internal class : storage 298 | //======================================================================== 299 | 300 | template 301 | class storage 302 | { 303 | public: 304 | 305 | template 306 | explicit storage(TT&& t, Guard&& guard) 307 | : m_value(std::forward(t)) 308 | { 309 | guard.release(); 310 | } 311 | explicit storage(const T& t) 312 | : m_value(t) 313 | { 314 | 315 | } 316 | explicit storage(T&& t) 317 | : m_value(std::move_if_noexcept(t)) 318 | { 319 | 320 | } 321 | T& ref() noexcept 322 | { 323 | return m_value; 324 | } 325 | const T& cref() const noexcept 326 | { 327 | return m_value; 328 | } 329 | T&& rref() noexcept 330 | { 331 | return std::move(m_value); 332 | } 333 | 334 | void set(const T& t) 335 | { 336 | m_value = t; 337 | } 338 | void set(T&& t) 339 | { 340 | m_value = move_if_noexcept_assignable(t); 341 | } 342 | 343 | private: 344 | 345 | T m_value; 346 | }; 347 | 348 | template 349 | class storage 350 | { 351 | public: 352 | 353 | template 354 | explicit storage(TT&& t, Guard&& guard) 355 | : m_value(t) 356 | { 357 | guard.release(); 358 | } 359 | template 360 | explicit storage(TT&& t) 361 | : m_value(std::forward(t)) 362 | { 363 | 364 | } 365 | 366 | T& ref() noexcept 367 | { 368 | return m_value.get(); 369 | } 370 | const T& cref() const noexcept 371 | { 372 | return m_value.get(); 373 | } 374 | T& rref() noexcept 375 | { 376 | return m_value.get(); 377 | } 378 | 379 | void set(T& t) 380 | { 381 | m_value = t; 382 | } 383 | 384 | private: 385 | 386 | std::reference_wrapper m_value; 387 | }; 388 | 389 | //======================================================================== 390 | // internal class : basic_scope_guard 391 | //======================================================================== 392 | 393 | template 394 | class basic_scope_guard : public ExitPolicy 395 | { 396 | public: 397 | template 398 | explicit basic_scope_guard(ExitFunction&& fn) 399 | noexcept(std::is_nothrow_move_constructible::value) 400 | : ExitPolicy{}, 401 | m_function{std::forward(fn)} 402 | { 403 | 404 | } 405 | 406 | basic_scope_guard(basic_scope_guard&& other) 407 | noexcept(std::is_nothrow_move_constructible::value) 408 | : ExitPolicy{other}, 409 | m_function{other.m_function.rref(), other} 410 | { 411 | 412 | } 413 | 414 | ~basic_scope_guard() 415 | noexcept(noexcept(std::declval()())) 416 | { 417 | if (should_execute()) { 418 | m_function.ref()(); 419 | } 420 | } 421 | 422 | basic_scope_guard(const basic_scope_guard&) = delete; 423 | basic_scope_guard& operator=(const basic_scope_guard&) = delete; 424 | basic_scope_guard& operator=(basic_scope_guard&&) = delete; 425 | 426 | //---------------------------------------------------------------------- 427 | // Modifiers 428 | //---------------------------------------------------------------------- 429 | public: 430 | 431 | using ExitPolicy::release; 432 | 433 | //---------------------------------------------------------------------- 434 | // Observers 435 | //---------------------------------------------------------------------- 436 | public: 437 | 438 | using ExitPolicy::should_execute; 439 | 440 | private: 441 | 442 | storage m_function; 443 | }; 444 | 445 | } // namespace detail 446 | 447 | //========================================================================== 448 | // class : scope_exit 449 | //========================================================================== 450 | 451 | //////////////////////////////////////////////////////////////////////////// 452 | /// \brief An exit handler for handling both sucess an error cases 453 | /// 454 | /// This will always execute the stored function unless 'release' has been 455 | /// called. 456 | /// 457 | /// When working in C++17 and above, you can use the automatic class 458 | /// type-deduction in order to create an instance of this: 459 | /// 460 | /// \code 461 | /// auto guard = scope::scope_exit{[&]{ 462 | /// ... 463 | /// }}; 464 | /// \endcode 465 | /// 466 | /// When working in any C++11 or C++14, convenience functions are also 467 | /// added to perform this deduction for you: 468 | /// 469 | /// \code 470 | /// auto guard = scope::make_scope_exit([&]{ 471 | /// ... 472 | /// }); 473 | /// \endcode 474 | /// 475 | /// \tparam Fn the function type for the scope to execute 476 | //////////////////////////////////////////////////////////////////////////// 477 | template 478 | class scope_exit 479 | : private detail::basic_scope_guard 480 | { 481 | using base_type = detail::basic_scope_guard; 482 | 483 | public: 484 | 485 | using base_type::base_type; 486 | 487 | using base_type::release; 488 | 489 | using base_type::should_execute; 490 | }; 491 | 492 | #if __cplusplus >= 201703L 493 | template 494 | scope_exit(Fn) -> scope_exit; 495 | #endif 496 | 497 | //========================================================================== 498 | // non-member functions : class : scope_exit 499 | //========================================================================== 500 | 501 | //-------------------------------------------------------------------------- 502 | // Utilities 503 | //-------------------------------------------------------------------------- 504 | 505 | /// \brief A convenience function for pre-C++17 compilers that deduces 506 | /// the underlying function type of the \c scope_exit 507 | /// 508 | /// It is recommended to always store the result of this into an \c auto 509 | /// variable: 510 | /// 511 | /// \code 512 | /// auto guard = scope::make_scope_exit([&]{ 513 | /// ... 514 | /// }); 515 | /// \endcode 516 | /// 517 | /// \param fn the function to create 518 | /// \return an instance of the scope_exit 519 | template 520 | SCOPE_NODISCARD 521 | scope_exit::type> 522 | make_scope_exit(Fn&& fn); 523 | 524 | //========================================================================== 525 | // class : scope_success 526 | //========================================================================== 527 | 528 | //////////////////////////////////////////////////////////////////////////// 529 | /// \brief An exit handler for handling non-throwing cases 530 | /// 531 | /// This will execute as long as an exception has not been thrown in the 532 | /// same frame the scope was created in. You can manually disengage 533 | /// executing this handler by calling \c release. 534 | /// 535 | /// When working in C++17 and above, you can use the automatic class 536 | /// type-deduction in order to create an instance of this: 537 | /// 538 | /// \code 539 | /// auto guard = scope::scope_success{[&]{ 540 | /// ... 541 | /// }}; 542 | /// \endcode 543 | /// 544 | /// When working in any C++11 or C++14, convenience functions are also 545 | /// added to perform this deduction for you: 546 | /// 547 | /// \code 548 | /// auto guard = scope::make_scope_success([&]{ 549 | /// ... 550 | /// }); 551 | /// \endcode 552 | /// 553 | /// \tparam Fn the function type for the scope to execute 554 | //////////////////////////////////////////////////////////////////////////// 555 | template 556 | class scope_success 557 | : private detail::basic_scope_guard 558 | { 559 | using base_type = detail::basic_scope_guard; 560 | 561 | public: 562 | 563 | using base_type::base_type; 564 | 565 | using base_type::release; 566 | 567 | using base_type::should_execute; 568 | }; 569 | 570 | #if __cplusplus >= 201703L 571 | template 572 | scope_success(Fn) -> scope_success; 573 | #endif 574 | 575 | //========================================================================== 576 | // non-member functions : class : scope_success 577 | //========================================================================== 578 | 579 | //-------------------------------------------------------------------------- 580 | // Utilities 581 | //-------------------------------------------------------------------------- 582 | 583 | /// \brief A convenience function for pre-C++17 compilers that deduces 584 | /// the underlying function type of the \c scope_success 585 | /// 586 | /// It is recommended to always store the result of this into an \c auto 587 | /// variable: 588 | /// 589 | /// \code 590 | /// auto guard = scope::make_scope_success([&]{ 591 | /// ... 592 | /// }); 593 | /// \endcode 594 | /// 595 | /// \param fn the function to create 596 | /// \return an instance of the scope_success 597 | template 598 | SCOPE_NODISCARD 599 | scope_success::type> 600 | make_scope_success(Fn&& fn); 601 | 602 | //========================================================================== 603 | // class : scope_fail 604 | //========================================================================== 605 | 606 | //////////////////////////////////////////////////////////////////////////// 607 | /// \brief An exit handler for handling throwing cases 608 | /// 609 | /// This will execute when an exception has been thrown in the same frame 610 | /// the scope was created in. You can manually disengage executing this 611 | /// handler by calling \c release. 612 | /// 613 | /// When working in C++17 and above, you can use the automatic class 614 | /// type-deduction in order to create an instance of this: 615 | /// 616 | /// \code 617 | /// auto guard = scope::scope_fail{[&]{ 618 | /// ... 619 | /// }}; 620 | /// \endcode 621 | /// 622 | /// When working in any C++11 or C++14, convenience functions are also 623 | /// added to perform this deduction for you: 624 | /// 625 | /// \code 626 | /// auto guard = scope::make_scope_fail([&]{ 627 | /// ... 628 | /// }); 629 | /// \endcode 630 | /// 631 | /// \tparam Fn the function type for the scope to execute 632 | //////////////////////////////////////////////////////////////////////////// 633 | template 634 | class scope_fail 635 | : private detail::basic_scope_guard 636 | { 637 | using base_type = detail::basic_scope_guard; 638 | 639 | public: 640 | 641 | using base_type::base_type; 642 | 643 | using base_type::release; 644 | 645 | using base_type::should_execute; 646 | }; 647 | 648 | #if __cplusplus >= 201703L 649 | template 650 | scope_fail(Fn) -> scope_fail; 651 | #endif 652 | 653 | //========================================================================== 654 | // non-member functions : class : scope_fail 655 | //========================================================================== 656 | 657 | //-------------------------------------------------------------------------- 658 | // Utilities 659 | //-------------------------------------------------------------------------- 660 | 661 | /// \brief A convenience function for pre-C++17 compilers that deduces 662 | /// the underlying function type of the \c scope_fail 663 | /// 664 | /// It is recommended to always store the result of this into an \c auto 665 | /// variable: 666 | /// 667 | /// \code 668 | /// auto guard = scope::make_scope_fail([&]{ 669 | /// ... 670 | /// }); 671 | /// \endcode 672 | /// 673 | /// \param fn the function to create 674 | /// \return an instance of the scope_fail 675 | template 676 | SCOPE_NODISCARD 677 | scope_fail::type> 678 | make_scope_fail(Fn&& fn); 679 | 680 | //========================================================================== 681 | // class : unique_resource 682 | //========================================================================== 683 | 684 | //////////////////////////////////////////////////////////////////////////// 685 | /// \brief A resource with unique-ownership semantics 686 | /// 687 | /// This wraps and owns the specified resource R and deletes it at the 688 | /// end of its lifetime with D. 689 | /// 690 | /// This utility is extremely useful for wrapping old C-style functions 691 | /// that return raw resources like pointers, or integer handles, since 692 | /// you can couple the destruction of the resource safely inside this 693 | /// class. For example: 694 | /// 695 | /// \code 696 | /// auto resource = scope::unique_resource{::open(...), &::close}; 697 | /// \endcode 698 | /// 699 | /// Or if you are pre-C++17, you can use: 700 | /// 701 | /// \code 702 | /// auto resource = scope::make_unique_resource(::open(...), &::close); 703 | /// \endcode 704 | /// 705 | /// \tparam R The resource type 706 | /// \tparam D The deleter type 707 | //////////////////////////////////////////////////////////////////////////// 708 | template 709 | class unique_resource 710 | { 711 | static_assert( 712 | (std::is_move_constructible::value && 713 | std::is_nothrow_move_constructible::value) || 714 | std::is_copy_constructible::value, 715 | "unique_resource must be nothrow_move_constructible or copy_constructible" 716 | ); 717 | static_assert( 718 | (std::is_move_constructible::value && 719 | std::is_nothrow_move_constructible::value) || 720 | std::is_copy_constructible::value, 721 | "deleter must be nothrow_move_constructible or copy_constructible" 722 | ); 723 | 724 | template 725 | using is_nothrow_move_or_copy_constructible_from 726 | = std::integral_constant::value || 727 | !std::is_nothrow_constructible::value) 728 | ? std::is_constructible::value 729 | : std::is_constructible::value>; 730 | 731 | template 732 | using enable_constructor_t = typename std::enable_if< 733 | is_nothrow_move_or_copy_constructible_from::value && 734 | is_nothrow_move_or_copy_constructible_from::value 735 | >::type; 736 | 737 | //------------------------------------------------------------------------ 738 | // Constructors / Destructor / Assignment 739 | //------------------------------------------------------------------------ 740 | public: 741 | 742 | using resource_type = typename std::remove_reference::type; 743 | using deleter_type = typename std::remove_reference::type; 744 | 745 | // Deleted default-constructor 746 | unique_resource() = delete; 747 | 748 | /// \brief Constructs a unique_resource from a given \p r resource and the 749 | /// \p deleter 750 | /// 751 | /// \param r the resource to own uniquely 752 | /// \param d the deleter to destroy the resource 753 | template > 755 | explicit unique_resource(RR&& r, DD&& d) 756 | noexcept(std::is_nothrow_constructible::value && 757 | std::is_nothrow_constructible::value); 758 | 759 | /// \brief Constructs a unique_resource by moving the contents of an 760 | /// existing resource 761 | /// 762 | /// The contents of the moved resource are now owned by this resource 763 | /// 764 | /// \param other the othe resource to move 765 | unique_resource(unique_resource&& other) 766 | noexcept(std::is_nothrow_move_constructible::value && 767 | std::is_nothrow_move_constructible::value); 768 | 769 | // Deleted copy constructor 770 | unique_resource(const unique_resource& other) = delete; 771 | 772 | //------------------------------------------------------------------------ 773 | 774 | /// \brief Equivalent to invoking \c reset() 775 | ~unique_resource(); 776 | 777 | //------------------------------------------------------------------------ 778 | 779 | /// \brief 780 | /// 781 | /// \param other 782 | /// \return \c reference to (*this) 783 | unique_resource& operator=(unique_resource&& other) 784 | noexcept(std::is_nothrow_assignable::value && 785 | std::is_nothrow_assignable::value); 786 | 787 | // Deleted copy assignment 788 | unique_resource& operator=(const unique_resource& other) = delete; 789 | 790 | //------------------------------------------------------------------------ 791 | // Modifiers 792 | //------------------------------------------------------------------------ 793 | public: 794 | 795 | /// \brief Resets the resource managed by this unique_resource 796 | /// 797 | /// This will invoke the stored deleter on the resource, if the resource 798 | /// has not already executed. 799 | void reset() noexcept; 800 | 801 | /// \brief Resets the resource managed by this unique_resource to the 802 | /// newly specified \p r resource 803 | /// 804 | /// The old resource is deleted with the stored deleter and replaced by 805 | /// the new one. 806 | /// 807 | /// \param r the new resource to store 808 | template 809 | void reset(RR&& r); 810 | 811 | /// \brief Releases the underlying stored resource from being managed by 812 | /// this unique_resource class 813 | /// 814 | /// \note by calling this function, the deleter of this class is no 815 | /// longer going to be invoked -- making it the responsibility of 816 | /// the caller to take care of this instead. 817 | void release() noexcept; 818 | 819 | //------------------------------------------------------------------------ 820 | // Observers 821 | //------------------------------------------------------------------------ 822 | public: 823 | 824 | /// \brief Gets a reference to the underlying resource 825 | /// 826 | /// \return a reference to the underlying resource 827 | const resource_type& get() const noexcept; 828 | 829 | /// \brief Gets a reference to the underlying deleter 830 | /// 831 | /// \return a reference to the underlying deleter 832 | const deleter_type& get_deleter() const noexcept; 833 | 834 | template 835 | typename std::enable_if::value,typename std::remove_pointer::type&>::type 836 | operator*() const noexcept; 837 | 838 | template 839 | typename std::enable_if::value,R>::type 840 | operator->() const noexcept; 841 | 842 | //------------------------------------------------------------------------ 843 | // Private Members 844 | //------------------------------------------------------------------------ 845 | private: 846 | 847 | detail::storage m_resource; 848 | detail::storage m_deleter; 849 | bool m_execute_on_destruction; 850 | 851 | //------------------------------------------------------------------------ 852 | // Private Member Functions 853 | //------------------------------------------------------------------------ 854 | private: 855 | 856 | template 857 | unique_resource(RR&& r, DD&& d, bool execute_on_destruction); 858 | 859 | void do_assign(std::true_type, std::true_type, unique_resource&& other); 860 | void do_assign(std::false_type, std::true_type, unique_resource&& other); 861 | void do_assign(std::true_type, std::false_type, unique_resource&& other); 862 | void do_assign(std::false_type, std::false_type, unique_resource&& other); 863 | 864 | template 865 | friend unique_resource::type, typename std::decay::type> 866 | make_unique_resource_checked(T&&, const S&, U&&); 867 | // noexcept(std::is_nothrow_constructible::type, T>::value && 868 | // std::is_nothrow_constructible::type, U>::value); 869 | 870 | }; 871 | 872 | #if __cplusplus >= 201703L 873 | 874 | // Template deduction guidelines 875 | template 876 | unique_resource(R, D) -> unique_resource; 877 | 878 | #endif /* __cplusplus >= 201703L */ 879 | 880 | //========================================================================== 881 | // non-member functions : class : unique_resource 882 | //========================================================================== 883 | 884 | //-------------------------------------------------------------------------- 885 | // Utilities 886 | //-------------------------------------------------------------------------- 887 | 888 | /// \brief 889 | /// 890 | /// Example: 891 | /// 892 | /// \code 893 | /// auto file = make_unique_resource_checked( 894 | /// ::fopen("potentially_nonexistent_file.txt", "r"), 895 | /// nullptr, 896 | /// &::fclose 897 | /// ); 898 | /// \endcode 899 | /// 900 | /// \param resource The resource to manager 901 | /// \param invalid The invalid resource to not delete 902 | /// \param d The deleter for the resource 903 | /// \return the constructed unique_resource 904 | template 905 | SCOPE_NODISCARD 906 | unique_resource::type, typename std::decay::type> 907 | make_unique_resource_checked(R&& resource, const S& invalid, D&& d); 908 | // noexcept(std::is_nothrow_constructible::type, R>::value && 909 | // std::is_nothrow_constructible::type, D>::value); 910 | 911 | /// \brief Makes a unique_resource from the specified \p resource and 912 | /// deleter \p d 913 | /// 914 | /// This function exists to allow for the unique_resource to be 915 | /// type-reduced. 916 | /// 917 | /// Example: 918 | /// 919 | /// \code 920 | /// auto file = make_unique_resource( 921 | /// ::fopen("potentially_nonexistent_file.txt", "r"), 922 | /// &::fclose 923 | /// ); 924 | /// \endcode 925 | /// 926 | /// \param resource The resource to manager 927 | /// \param d The deleter for the resource 928 | /// \return the constructed unique_resource 929 | template 930 | SCOPE_NODISCARD 931 | unique_resource::type, typename std::decay::type> 932 | make_unique_resource(R&& resource, D&& d) 933 | noexcept(std::is_nothrow_constructible::type, R>::value && 934 | std::is_nothrow_constructible::type, D>::value); 935 | 936 | } // namespace scope 937 | 938 | template 939 | scope::scope_exit::type> 940 | scope::make_scope_exit(Fn&& fn) 941 | { 942 | using result_type = scope_exit::type>; 943 | 944 | return result_type{std::forward(fn)}; 945 | } 946 | 947 | template 948 | scope::scope_success::type> 949 | scope::make_scope_success(Fn&& fn) 950 | { 951 | using result_type = scope_success::type>; 952 | 953 | return result_type{std::forward(fn)}; 954 | } 955 | 956 | template 957 | scope::scope_fail::type> 958 | scope::make_scope_fail(Fn&& fn) 959 | { 960 | using result_type = scope_fail::type>; 961 | 962 | return result_type{std::forward(fn)}; 963 | } 964 | 965 | //============================================================================ 966 | // definition : class : unique_resource 967 | //============================================================================ 968 | 969 | //---------------------------------------------------------------------------- 970 | // Constructors / Destructor / Assignment 971 | //---------------------------------------------------------------------------- 972 | 973 | template 974 | template 975 | inline scope::unique_resource::unique_resource(RR&& r, DD&& d) 976 | noexcept(std::is_nothrow_constructible::value && 977 | std::is_nothrow_constructible::value) 978 | : unique_resource{std::forward(r), std::forward
(d), true} 979 | { 980 | 981 | } 982 | 983 | template 984 | inline scope::unique_resource::unique_resource(unique_resource&& other) 985 | noexcept(std::is_nothrow_move_constructible::value && 986 | std::is_nothrow_move_constructible::value) 987 | : m_resource{other.m_resource.rref(), make_scope_exit([]{})}, 988 | m_deleter{other.m_deleter.rref(), make_scope_exit([&other,this] 989 | { 990 | if (other.m_execute_on_destruction) { 991 | auto& deleter = m_deleter.ref(); 992 | deleter(m_resource.ref()); 993 | other.release(); 994 | } 995 | })}, 996 | m_execute_on_destruction{other.m_execute_on_destruction} 997 | { 998 | other.m_execute_on_destruction = false; 999 | } 1000 | //---------------------------------------------------------------------------- 1001 | 1002 | template 1003 | inline scope::unique_resource::~unique_resource() 1004 | { 1005 | reset(); 1006 | } 1007 | 1008 | //---------------------------------------------------------------------------- 1009 | 1010 | template 1011 | inline scope::unique_resource& 1012 | scope::unique_resource::operator=(unique_resource&& other) 1013 | noexcept(std::is_nothrow_assignable::value && 1014 | std::is_nothrow_assignable::value) 1015 | { 1016 | if (&other == this ) return (*this); 1017 | 1018 | do_assign( 1019 | std::is_nothrow_move_assignable{}, 1020 | std::is_nothrow_move_assignable{}, 1021 | std::move(other) 1022 | ); 1023 | m_execute_on_destruction = other.m_execute_on_destruction; 1024 | other.m_execute_on_destruction = false; 1025 | return (*this); 1026 | } 1027 | 1028 | //---------------------------------------------------------------------------- 1029 | // Modifiers 1030 | //---------------------------------------------------------------------------- 1031 | 1032 | template 1033 | inline void scope::unique_resource::reset() 1034 | noexcept 1035 | { 1036 | if (m_execute_on_destruction) { 1037 | m_execute_on_destruction = false; 1038 | 1039 | auto& deleter = m_deleter.ref(); 1040 | deleter( m_resource.ref() ); 1041 | } 1042 | } 1043 | 1044 | template 1045 | template 1046 | inline void scope::unique_resource::reset(RR&& r) 1047 | { 1048 | auto guard = make_scope_fail([&r, this]{ 1049 | auto& deleter = m_deleter.ref(); 1050 | deleter(r); 1051 | }); 1052 | (void) guard; 1053 | 1054 | reset(); 1055 | m_resource.set( std::forward(r) ); 1056 | m_execute_on_destruction = true; 1057 | } 1058 | 1059 | 1060 | template 1061 | inline void scope::unique_resource::release() 1062 | noexcept 1063 | { 1064 | m_execute_on_destruction = false; 1065 | } 1066 | 1067 | //---------------------------------------------------------------------------- 1068 | // Observers 1069 | //---------------------------------------------------------------------------- 1070 | 1071 | template 1072 | inline const typename scope::unique_resource::resource_type& 1073 | scope::unique_resource::get() 1074 | const noexcept 1075 | { 1076 | return m_resource.cref(); 1077 | } 1078 | 1079 | template 1080 | inline const typename scope::unique_resource::deleter_type& 1081 | scope::unique_resource::get_deleter() 1082 | const noexcept 1083 | { 1084 | return m_deleter.cref(); 1085 | } 1086 | 1087 | template 1088 | template 1089 | inline typename std::enable_if::value,typename std::remove_pointer::type&>::type 1090 | scope::unique_resource::operator*() 1091 | const noexcept 1092 | { 1093 | return (*get()); 1094 | } 1095 | 1096 | template 1097 | template 1098 | inline typename std::enable_if::value,R>::type 1099 | scope::unique_resource::operator->() 1100 | const noexcept 1101 | { 1102 | return get(); 1103 | } 1104 | 1105 | 1106 | //---------------------------------------------------------------------------- 1107 | // Private Member Functions 1108 | //---------------------------------------------------------------------------- 1109 | 1110 | template 1111 | template 1112 | inline scope::unique_resource::unique_resource(RR&& r, 1113 | DD&& d, 1114 | bool execute_on_destruction) 1115 | : m_resource{std::forward(r), make_scope_exit([&d,&r] 1116 | { 1117 | d(r); 1118 | })}, 1119 | m_deleter{std::forward
(d), make_scope_exit([&d,this]{ 1120 | d(m_resource.ref()); 1121 | })}, 1122 | m_execute_on_destruction{execute_on_destruction} 1123 | { 1124 | 1125 | } 1126 | 1127 | //---------------------------------------------------------------------------- 1128 | 1129 | template 1130 | inline void scope::unique_resource::do_assign(std::true_type, 1131 | std::true_type, 1132 | unique_resource&& other) 1133 | { 1134 | m_resource = std::move(other.resource); 1135 | m_deleter = std::move(other.deleter); 1136 | } 1137 | 1138 | template 1139 | inline void scope::unique_resource::do_assign(std::false_type, 1140 | std::true_type, 1141 | unique_resource&& other) 1142 | { 1143 | m_resource = detail::as_const(other.resource); 1144 | m_deleter = std::move(other.deleter); 1145 | } 1146 | 1147 | template 1148 | inline void scope::unique_resource::do_assign(std::true_type, 1149 | std::false_type, 1150 | unique_resource&& other) 1151 | { 1152 | m_resource = std::move(other.resource); 1153 | m_deleter = detail::as_const(other.deleter); 1154 | } 1155 | 1156 | template 1157 | inline void scope::unique_resource::do_assign(std::false_type, 1158 | std::false_type, 1159 | unique_resource&& other) 1160 | { 1161 | m_resource = detail::as_const(other.resource); 1162 | m_deleter = detail::as_const(other.deleter); 1163 | } 1164 | 1165 | //============================================================================ 1166 | // definitions : non-member functions : class : unique_resource 1167 | //============================================================================ 1168 | 1169 | //---------------------------------------------------------------------------- 1170 | // Utilities 1171 | //---------------------------------------------------------------------------- 1172 | 1173 | template 1174 | inline scope::unique_resource::type, typename std::decay::type> 1175 | scope::make_unique_resource_checked(R&& resource, const S& invalid, D&& d) 1176 | // noexcept(std::is_nothrow_constructible::type, R>::value && 1177 | // std::is_nothrow_constructible::type, D>::value) 1178 | { 1179 | using resource_type = typename std::decay::type; 1180 | using deleter_type = typename std::decay::type; 1181 | using result_type = scope::unique_resource; 1182 | 1183 | const auto execute_on_destruction = !(resource == invalid); 1184 | 1185 | return result_type{ 1186 | std::forward(resource), 1187 | std::forward(d), 1188 | execute_on_destruction 1189 | }; 1190 | } 1191 | 1192 | template 1193 | inline scope::unique_resource::type, typename std::decay::type> 1194 | scope::make_unique_resource(R&& resource, D&& d) 1195 | noexcept(std::is_nothrow_constructible::type, R>::value && 1196 | std::is_nothrow_constructible::type, D>::value) 1197 | { 1198 | using resource_type = typename std::decay::type; 1199 | using deleter_type = typename std::decay::type; 1200 | using result_type = scope::unique_resource; 1201 | 1202 | return result_type{ 1203 | std::forward(resource), 1204 | std::forward(d) 1205 | }; 1206 | } 1207 | 1208 | #endif /* SCOPE_SCOPE_HPP */ --------------------------------------------------------------------------------