├── bazel ├── BUILD.bazel ├── repos.bzl └── deps.bzl ├── 3rdparty ├── stout-atomic-backoff │ ├── BUILD.bazel │ └── repos.bzl └── stout-stateful-tally │ ├── BUILD.bazel │ └── repos.bzl ├── .bazelrc ├── stout ├── borrowable.h └── borrowed_ptr.h ├── WORKSPACE.bazel ├── test ├── BUILD.bazel └── borrowed_ptr.cc ├── BUILD.bazel ├── README.md └── LICENSE /bazel/BUILD.bazel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /3rdparty/stout-atomic-backoff/BUILD.bazel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /3rdparty/stout-stateful-tally/BUILD.bazel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # Specific Bazel build/test options. 2 | 3 | build --cxxopt='-std=c++17' 4 | -------------------------------------------------------------------------------- /stout/borrowable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stout/borrowed_ptr.h" 4 | 5 | // TODO(benh): remove this file? 6 | -------------------------------------------------------------------------------- /WORKSPACE.bazel: -------------------------------------------------------------------------------- 1 | load("//bazel:repos.bzl", "repos") 2 | 3 | repos(external = False) 4 | 5 | load("//bazel:deps.bzl", "deps") 6 | 7 | deps() 8 | -------------------------------------------------------------------------------- /test/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_test") 2 | 3 | cc_test( 4 | name = "borrowed_ptr", 5 | srcs = ["borrowed_ptr.cc"], 6 | deps = [ 7 | "//:borrowed_ptr", 8 | "@com_github_google_googletest//:gtest_main", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_library") 2 | 3 | cc_library( 4 | name = "borrowed_ptr", 5 | hdrs = [ 6 | "stout/borrowable.h", 7 | "stout/borrowed_ptr.h", 8 | ], 9 | visibility = ["//visibility:public"], 10 | deps = [ 11 | "@com_github_3rdparty_stout_stateful_tally//:stateful-tally", 12 | "@com_github_google_glog//:glog", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /3rdparty/stout-atomic-backoff/repos.bzl: -------------------------------------------------------------------------------- 1 | """Adds repositories/archives.""" 2 | 3 | ######################################################################## 4 | # DO NOT EDIT THIS FILE unless you are inside the 5 | # https://github.com/3rdparty/stout-atomic-backoff repository. If you 6 | # encounter it anywhere else it is because it has been copied there in 7 | # order to simplify adding transitive dependencies. If you want a 8 | # different version of stout-atomic-backoff follow the Bazel build 9 | # instructions at https://github.com/3rdparty/stout-atomic-backoff. 10 | ######################################################################## 11 | 12 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") 13 | 14 | def repos(external = True, repo_mapping = {}): 15 | if external and "com_github_3rdparty_stout_atomic_backoff" not in native.existing_rules(): 16 | git_repository( 17 | name = "com_github_3rdparty_stout_atomic_backoff", 18 | remote = "https://github.com/3rdparty/stout-atomic-backoff", 19 | commit = "b4d3d4d714f6c57fa0faedb0eff6ff32bb772fda", 20 | shallow_since = "1629613266 +0200", 21 | repo_mapping = repo_mapping, 22 | ) 23 | -------------------------------------------------------------------------------- /bazel/repos.bzl: -------------------------------------------------------------------------------- 1 | """Adds repositories/archives.""" 2 | 3 | ######################################################################## 4 | # DO NOT EDIT THIS FILE unless you are inside the 5 | # https://github.com/3rdparty/stout-borrowed-ptr repository. If you 6 | # encounter it anywhere else it is because it has been copied there in 7 | # order to simplify adding transitive dependencies. If you want a 8 | # different version of stout-borrowed-ptr follow the Bazel build 9 | # instructions at https://github.com/3rdparty/stout-borrowed-ptr. 10 | ######################################################################## 11 | 12 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") 13 | load("//3rdparty/stout-stateful-tally:repos.bzl", stout_stateful_tally_repos = "repos") 14 | 15 | def repos(external = True, repo_mapping = {}): 16 | stout_stateful_tally_repos() 17 | 18 | if external and "com_github_3rdparty_stout_borrowed_ptr" not in native.existing_rules(): 19 | git_repository( 20 | name = "com_github_3rdparty_stout_borrowed_ptr", 21 | remote = "https://github.com/3rdparty/stout-borrowed-ptr", 22 | commit = "975695587aaff011666db21d3470299b92331825", 23 | shallow_since = "1643741357 -0800", 24 | repo_mapping = repo_mapping, 25 | ) 26 | -------------------------------------------------------------------------------- /3rdparty/stout-stateful-tally/repos.bzl: -------------------------------------------------------------------------------- 1 | """Adds repositories/archives.""" 2 | 3 | ######################################################################## 4 | # DO NOT EDIT THIS FILE unless you are inside the 5 | # https://github.com/3rdparty/stout-stateful-tally repository. If you 6 | # encounter it anywhere else it is because it has been copied there in 7 | # order to simplify adding transitive dependencies. If you want a 8 | # different version of stout-stateful-tally follow the Bazel build 9 | # instructions at https://github.com/3rdparty/stout-stateful-tally. 10 | ######################################################################## 11 | 12 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") 13 | load("//3rdparty/stout-atomic-backoff:repos.bzl", stout_atomic_backoff_repos = "repos") 14 | 15 | def repos(external = True, repo_mapping = {}): 16 | stout_atomic_backoff_repos( 17 | repo_mapping = repo_mapping, 18 | ) 19 | 20 | if external and "com_github_3rdparty_stout_stateful_tally" not in native.existing_rules(): 21 | git_repository( 22 | name = "com_github_3rdparty_stout_stateful_tally", 23 | remote = "https://github.com/3rdparty/stout-stateful-tally", 24 | commit = "28529b678f9b48d1ca83a22e81ad71d5b25fa888", 25 | shallow_since = "1629613344 +0200", 26 | repo_mapping = repo_mapping, 27 | ) 28 | -------------------------------------------------------------------------------- /bazel/deps.bzl: -------------------------------------------------------------------------------- 1 | """Dependency specific initialization.""" 2 | 3 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 4 | load("@com_github_3rdparty_stout_stateful_tally//bazel:deps.bzl", stout_stateful_tally_deps = "deps") 5 | 6 | def deps(repo_mapping = {}): 7 | """Propagate all dependencies. 8 | 9 | Args: 10 | repo_mapping (str): {}. 11 | """ 12 | 13 | stout_stateful_tally_deps( 14 | repo_mapping = repo_mapping, 15 | ) 16 | 17 | if "com_github_gflags_gflags" not in native.existing_rules(): 18 | http_archive( 19 | name = "com_github_gflags_gflags", 20 | url = "https://github.com/gflags/gflags/archive/v2.2.2.tar.gz", 21 | sha256 = "34af2f15cf7367513b352bdcd2493ab14ce43692d2dcd9dfc499492966c64dcf", 22 | strip_prefix = "gflags-2.2.2", 23 | ) 24 | 25 | if "com_github_google_glog" not in native.existing_rules(): 26 | http_archive( 27 | name = "com_github_google_glog", 28 | url = "https://github.com/google/glog/archive/v0.4.0.tar.gz", 29 | sha256 = "f28359aeba12f30d73d9e4711ef356dc842886968112162bc73002645139c39c", 30 | strip_prefix = "glog-0.4.0", 31 | ) 32 | 33 | if "com_github_google_googletest" not in native.existing_rules(): 34 | http_archive( 35 | name = "com_github_google_googletest", 36 | url = "https://github.com/google/googletest/archive/release-1.10.0.tar.gz", 37 | sha256 = "9dc9157a9a1551ec7a7e43daea9a694a0bb5fb8bec81235d8a1e6ef64c716dcb", 38 | strip_prefix = "googletest-release-1.10.0", 39 | repo_mapping = repo_mapping, 40 | ) 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![status: archive](https://github.com/GIScience/badges/raw/master/status/archive.svg)](https://github.com/GIScience/badges#archive) [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | 3 | # DEPRECATED - migrated into [`3rdparty/stout`](https://github.com/3rdparty/stout). 4 | 5 | # `stout::borrowed_ptr` 6 | 7 | `stout::borrowed_ptr` fills a gap between `std::unique_ptr` and `std::shared_ptr`. 8 | 9 | ***It is intended to help programmers write correct and self-documenting code wrt ownership semantics where you might have otherwise used pointers and relied on documentation.*** 10 | 11 | It is especially useful for *asynchronous code*. 12 | 13 | Properties: 14 | 15 | * Like a `std::unique_ptr`, a `stout::borrowed_ptr` is not copyable but must be moved. 16 | 17 | * Like a `std::shared_ptr`, there can be more than one instance of `stout::borrowed_ptr` that point to the same thing. 18 | 19 | * Unlike `std::shared_ptr`, because `stout::borrowed_ptr` is not copyable the reference count for the pointer is only decremented when the `stout::borrowed_ptr` relinquishes borrowing or is destructed without having already called `relinquish()`. 20 | 21 | 22 | ## Usage 23 | 24 | You "borrow" a pointer using `stout::borrow`: 25 | 26 | ```cpp 27 | // Assuming you have some pointer 'data'. 28 | auto borrowed = stout::borrow(data, [](auto*) { 29 | // Invoked when 'borrowed' has been relinquished or been destructed. 30 | }); 31 | ``` 32 | 33 | This is great for callbacks that are asynchronous, i.e., when the call returns you can't be sure that the callee doesn't still need to use the pointer you passed. 34 | 35 | ```cpp 36 | callback(std::move(borrowed)); 37 | 38 | // 'borrowed' might still be getting used! 39 | ``` 40 | 41 | The callee can either let their `stout::borrowed_ptr` go out of scope or they can explicitly relinquish borrowing by calling `relinquish()`: 42 | 43 | ```cpp 44 | void callback(stout::borrowed_ptr&& data) { 45 | std::async([data = std::move(data)]() { 46 | // Do some processing with 'data'. 47 | data.relinquish(); 48 | // Do more processing without 'data'. 49 | }); 50 | } 51 | ``` 52 | 53 | You can create multiple instances of `stout::borrowed_ptr` when you want to share it between multiple callees: 54 | 55 | ```cpp 56 | auto borrows = stout::borrow(4, data, [](auto*) { 57 | // Invoked when ALL borrowers have relinquished. 58 | }); 59 | 60 | for (auto&& borrowed : std::move(borrows)) { 61 | std::async([borrowed = std::move(borrowed)]() { 62 | // ... borrowed.relinquish(); 63 | }); 64 | } 65 | ``` 66 | 67 | You can use `std::unique_ptr` with borrowing semantics as well. If you don't care about using the `std::unique_ptr` after borrowing has been relinquished you can simply do: 68 | 69 | ```cpp 70 | std::unique_ptr data = ...; 71 | 72 | auto borrowed = stout::borrow(std::move(data)); 73 | ``` 74 | 75 | However, if you want to "re-own" after borrowing has been relinquished then you can do: 76 | 77 | ```cpp 78 | std::unique_ptr data = ...; 79 | 80 | auto borrowed = stout::borrow(std::move(data), [](std::unique_ptr&& data) { 81 | // Can now use 'data' knowing there are no borrowers. 82 | }); 83 | ``` -------------------------------------------------------------------------------- /test/borrowed_ptr.cc: -------------------------------------------------------------------------------- 1 | #include "stout/borrowed_ptr.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "gmock/gmock.h" 9 | #include "gtest/gtest.h" 10 | #include "stout/borrowable.h" 11 | 12 | using std::atomic; 13 | using std::function; 14 | using std::string; 15 | using std::thread; 16 | using std::unique_ptr; 17 | using std::vector; 18 | 19 | using stout::Borrowable; 20 | using stout::borrowed_ptr; 21 | using stout::borrowed_ref; 22 | using stout::enable_borrowable_from_this; 23 | 24 | using testing::_; 25 | using testing::MockFunction; 26 | 27 | TEST(BorrowTest, BorrowRef) { 28 | Borrowable s("hello world"); 29 | 30 | MockFunction mock; 31 | 32 | EXPECT_CALL(mock, Call()) 33 | .Times(1); 34 | 35 | borrowed_ref borrowed = s.Borrow(); 36 | 37 | EXPECT_EQ(s.borrows(), 1); 38 | 39 | // NOTE: after a move we only expect a single borrow! 40 | borrowed_ref moved = std::move(borrowed); 41 | 42 | EXPECT_EQ(s.borrows(), 1); 43 | 44 | EXPECT_EQ("hello world", *moved); 45 | 46 | s.Watch(mock.AsStdFunction()); 47 | } 48 | 49 | 50 | TEST(BorrowTest, BorrowPtr) { 51 | Borrowable s("hello world"); 52 | 53 | MockFunction mock; 54 | 55 | EXPECT_CALL(mock, Call()) 56 | .Times(1); 57 | 58 | borrowed_ptr borrowed = s.Borrow(); 59 | 60 | // NOTE: after a move we only expect a single borrow! 61 | borrowed_ptr moved = std::move(borrowed); 62 | 63 | EXPECT_EQ("hello world", *moved); 64 | 65 | s.Watch(mock.AsStdFunction()); 66 | } 67 | 68 | 69 | TEST(BorrowTest, ConstBorrowPtr) { 70 | Borrowable s("hello world"); 71 | 72 | MockFunction mock; 73 | 74 | EXPECT_CALL(mock, Call()) 75 | .Times(1); 76 | 77 | borrowed_ptr borrowed = s.Borrow(); 78 | 79 | s.Watch(mock.AsStdFunction()); 80 | } 81 | 82 | 83 | TEST(BorrowTest, Reborrow) { 84 | Borrowable s("hello world"); 85 | 86 | MockFunction mock; 87 | 88 | EXPECT_CALL(mock, Call()) 89 | .Times(1); 90 | 91 | borrowed_ptr borrowed = s.Borrow(); 92 | 93 | s.Watch(mock.AsStdFunction()); 94 | 95 | borrowed_ptr reborrow = borrowed.reborrow(); 96 | 97 | EXPECT_TRUE(reborrow); 98 | } 99 | 100 | 101 | TEST(BorrowTest, Emplace) { 102 | struct S { 103 | S(borrowed_ptr i) 104 | : i_(std::move(i)) {} 105 | 106 | S(const S& that) 107 | : i_(that.i_.reborrow()) {} 108 | 109 | borrowed_ptr i_; 110 | }; 111 | 112 | Borrowable i(42); 113 | 114 | MockFunction mock; 115 | 116 | EXPECT_CALL(mock, Call()) 117 | .Times(1); 118 | 119 | vector> vector; 120 | 121 | vector.emplace_back(i.Borrow()); 122 | 123 | i.Watch(mock.AsStdFunction()); 124 | } 125 | 126 | 127 | TEST(BorrowTest, MultipleBorrows) { 128 | Borrowable s("hello world"); 129 | 130 | MockFunction mock; 131 | 132 | EXPECT_CALL(mock, Call()) 133 | .Times(0); 134 | 135 | vector> borrows; 136 | 137 | borrows.push_back(s.Borrow()); 138 | borrows.push_back(s.Borrow()); 139 | borrows.push_back(s.Borrow()); 140 | borrows.push_back(s.Borrow()); 141 | 142 | s.Watch(mock.AsStdFunction()); 143 | 144 | vector threads; 145 | 146 | atomic wait(true); 147 | 148 | while (!borrows.empty()) { 149 | threads.push_back(thread([&wait, borrowed = std::move(borrows.back())]() { 150 | while (wait.load()) {} 151 | // ... destructor will invoke borrowed.relinquish(). 152 | })); 153 | 154 | borrows.pop_back(); 155 | } 156 | 157 | EXPECT_CALL(mock, Call()) 158 | .Times(1); 159 | 160 | wait.store(false); 161 | 162 | for (auto&& thread : threads) { 163 | thread.join(); 164 | } 165 | } 166 | 167 | 168 | TEST(BorrowTest, MultipleConstBorrows) { 169 | Borrowable s("hello world"); 170 | 171 | MockFunction mock; 172 | 173 | EXPECT_CALL(mock, Call()) 174 | .Times(0); 175 | 176 | vector> borrows; 177 | 178 | borrows.push_back(s.Borrow()); 179 | borrows.push_back(s.Borrow()); 180 | borrows.push_back(s.Borrow()); 181 | borrows.push_back(s.Borrow()); 182 | 183 | s.Watch(mock.AsStdFunction()); 184 | 185 | vector threads; 186 | 187 | atomic wait(true); 188 | 189 | while (!borrows.empty()) { 190 | threads.push_back(thread([&wait, borrowed = std::move(borrows.back())]() { 191 | while (wait.load()) {} 192 | // ... destructor will invoke borrowed.relinquish(). 193 | })); 194 | 195 | borrows.pop_back(); 196 | } 197 | 198 | EXPECT_CALL(mock, Call()) 199 | .Times(1); 200 | 201 | wait.store(false); 202 | 203 | for (auto&& thread : threads) { 204 | thread.join(); 205 | } 206 | } 207 | 208 | 209 | TEST(BorrowTest, BorrowedPtrUpcast) { 210 | struct Base { 211 | int i = 42; 212 | }; 213 | 214 | struct Derived : public Base {}; 215 | 216 | Borrowable derived; 217 | 218 | MockFunction mock; 219 | 220 | EXPECT_CALL(mock, Call()) 221 | .Times(1); 222 | 223 | borrowed_ptr base = derived.Borrow(); 224 | 225 | base->i++; 226 | 227 | EXPECT_EQ(43, base->i); 228 | 229 | derived.Watch(mock.AsStdFunction()); 230 | } 231 | 232 | 233 | TEST(BorrowTest, BorrowedPtrConstUpcast) { 234 | struct Base { 235 | int i = 42; 236 | }; 237 | 238 | struct Derived : public Base {}; 239 | 240 | Borrowable derived; 241 | 242 | MockFunction mock; 243 | 244 | EXPECT_CALL(mock, Call()) 245 | .Times(1); 246 | 247 | borrowed_ptr base = derived.Borrow(); 248 | 249 | EXPECT_EQ(42, base->i); 250 | 251 | derived.Watch(mock.AsStdFunction()); 252 | } 253 | 254 | 255 | TEST(BorrowTest, ConstBorrowedPtrUpcast) { 256 | struct Base { 257 | int i = 42; 258 | }; 259 | 260 | struct Derived : public Base {}; 261 | 262 | Borrowable derived; 263 | 264 | MockFunction mock; 265 | 266 | EXPECT_CALL(mock, Call()) 267 | .Times(1); 268 | 269 | borrowed_ptr base = derived.Borrow(); 270 | 271 | EXPECT_EQ(42, base->i); 272 | 273 | derived.Watch(mock.AsStdFunction()); 274 | } 275 | 276 | 277 | TEST(BorrowTest, MoveBorrowable) { 278 | Borrowable s("hello world"); 279 | 280 | borrowed_ptr borrowed = s.Borrow(); 281 | 282 | atomic moving(false); 283 | 284 | auto t = thread([&]() { 285 | moving.store(true); 286 | Borrowable moved = std::move(s); 287 | moving.store(false); 288 | }); 289 | 290 | while (!moving.load()) {} 291 | 292 | EXPECT_EQ("hello world", *s); 293 | 294 | borrowed_ptr reborrowed = borrowed.reborrow(); 295 | 296 | borrowed.relinquish(); 297 | 298 | EXPECT_TRUE(moving.load()); 299 | 300 | reborrowed.relinquish(); 301 | 302 | t.join(); 303 | 304 | EXPECT_FALSE(moving.load()); 305 | 306 | EXPECT_EQ("", *s); 307 | } 308 | 309 | 310 | TEST(BorrowTest, CallableMove) { 311 | Borrowable s("hello world"); 312 | 313 | auto callable = s.Borrow([&]() { 314 | EXPECT_EQ(s.borrows(), 1); 315 | }); 316 | 317 | EXPECT_EQ(s.borrows(), 1); 318 | 319 | { 320 | auto moved = std::move(callable); 321 | 322 | EXPECT_EQ(s.borrows(), 1); 323 | 324 | moved(); 325 | } 326 | 327 | EXPECT_EQ(s.borrows(), 0); 328 | } 329 | 330 | 331 | TEST(BorrowTest, CallableCopy) { 332 | Borrowable s("hello world"); 333 | 334 | auto callable = s.Borrow([&]() { 335 | EXPECT_EQ(s.borrows(), 2); 336 | }); 337 | 338 | EXPECT_EQ(s.borrows(), 1); 339 | 340 | { 341 | auto copy = callable; 342 | 343 | EXPECT_EQ(s.borrows(), 2); 344 | 345 | copy(); 346 | } 347 | 348 | EXPECT_EQ(s.borrows(), 1); 349 | } 350 | 351 | 352 | TEST(BorrowTest, EnableBorrowableFromThis) { 353 | class Foo : public enable_borrowable_from_this { 354 | public: 355 | Foo(int i) 356 | : i(i) {} 357 | int i = 0; 358 | }; 359 | 360 | Foo foo(42); 361 | 362 | EXPECT_EQ(foo.borrows(), 0); 363 | 364 | borrowed_ptr borrowed = foo.Borrow(); 365 | 366 | EXPECT_EQ(foo.borrows(), 1); 367 | 368 | EXPECT_EQ(borrowed->i, 42); 369 | } 370 | 371 | 372 | TEST(BorrowTest, EnableBorrowableFromThisMove) { 373 | class Foo : public enable_borrowable_from_this { 374 | public: 375 | Foo(int i) 376 | : i(i) {} 377 | int i = 0; 378 | }; 379 | 380 | Foo foo(42); 381 | 382 | Foo moved = std::move(foo); 383 | 384 | EXPECT_EQ(moved.borrows(), 0); 385 | 386 | borrowed_ptr borrowed = moved.Borrow(); 387 | 388 | EXPECT_EQ(moved.borrows(), 1); 389 | 390 | EXPECT_EQ(borrowed->i, 42); 391 | } 392 | 393 | 394 | TEST(BorrowTest, EnableBorrowableFromThisCopy) { 395 | class Foo : public enable_borrowable_from_this { 396 | public: 397 | Foo(int i) 398 | : i(i) {} 399 | int i = 0; 400 | }; 401 | 402 | Foo foo(42); 403 | 404 | Foo copy = foo; 405 | 406 | EXPECT_EQ(copy.borrows(), 0); 407 | 408 | borrowed_ptr borrowed = copy.Borrow(); 409 | 410 | EXPECT_EQ(copy.borrows(), 1); 411 | 412 | EXPECT_EQ(borrowed->i, 42); 413 | } 414 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /stout/borrowed_ptr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "glog/logging.h" 6 | #include "stout/stateful-tally.h" 7 | 8 | //////////////////////////////////////////////////////////////////////// 9 | 10 | namespace stout { 11 | 12 | //////////////////////////////////////////////////////////////////////// 13 | 14 | // Forward dependencies. 15 | template 16 | class borrowed_ref; 17 | 18 | template 19 | class borrowed_ptr; 20 | 21 | template 22 | class borrowed_callable; 23 | 24 | //////////////////////////////////////////////////////////////////////// 25 | 26 | // NOTE: currently this implementation of Borrowable does an atomic 27 | // backoff instead of blocking the thread when the destructor waits 28 | // for all borrows to be relinquished. This will be much less 29 | // efficient (and hold up a CPU) if the borrowers take a while to 30 | // relinquish. However, since Borrowable will mostly be used in 31 | // cirumstances where the tally is definitely back to 0 when we wait 32 | // no backoff will occur. For circumstances where Borrowable is being 33 | // used to wait until work is completed consider using a Notification 34 | // to be notified when the work is complete and then Borrowable should 35 | // destruct without any atomic backoff (because any workers/threads 36 | // will have relinquished). 37 | class TypeErasedBorrowable { 38 | public: 39 | template 40 | bool Watch(F&& f) { 41 | auto [state, count] = tally_.Wait([](auto, size_t) { return true; }); 42 | 43 | do { 44 | if (state == State::Watching) { 45 | return false; 46 | } else if (count == 0) { 47 | f(); 48 | return true; 49 | } 50 | 51 | CHECK_EQ(state, State::Borrowing); 52 | 53 | } while (!tally_.Update(state, count, State::Watching, count + 1)); 54 | 55 | watch_ = std::move(f); 56 | 57 | Relinquish(); 58 | 59 | return true; 60 | } 61 | 62 | void WaitUntilBorrowsEquals(size_t borrows) { 63 | tally_.Wait([&](auto /* state */, size_t count) { 64 | return count == borrows; 65 | }); 66 | } 67 | 68 | size_t borrows() { 69 | return tally_.count(); 70 | } 71 | 72 | void Relinquish() { 73 | auto [state, count] = tally_.Decrement(); 74 | 75 | if (state == State::Watching && count == 0) { 76 | // Move out 'watch_' in case it gets reset either in the 77 | // callback or because a concurrent call to 'borrow()' occurs 78 | // after we've updated the tally below. 79 | auto f = std::move(watch_); 80 | watch_ = std::function(); 81 | 82 | tally_.Update(state, State::Borrowing); 83 | 84 | // At this point a call to 'borrow()' may mean that there are 85 | // outstanding 'borrowed_ref/ptr' when the watch callback gets 86 | // invoked and thus it's up to the users of this abstraction to 87 | // avoid making calls to 'borrow()' until after the watch 88 | // callback gets invoked if they want to guarantee that there 89 | // are no outstanding 'borrowed_ref/ptr'. 90 | 91 | f(); 92 | } 93 | } 94 | 95 | protected: 96 | TypeErasedBorrowable() 97 | : tally_(State::Borrowing) {} 98 | 99 | TypeErasedBorrowable(const TypeErasedBorrowable& that) 100 | : tally_(State::Borrowing) {} 101 | 102 | TypeErasedBorrowable(TypeErasedBorrowable&& that) 103 | : tally_(State::Borrowing) { 104 | // We need to wait until all borrows have been relinquished so 105 | // any memory associated with 'that' can be safely released. 106 | that.WaitUntilBorrowsEquals(0); 107 | } 108 | 109 | virtual ~TypeErasedBorrowable() { 110 | auto state = State::Borrowing; 111 | if (!tally_.Update(state, State::Destructing)) { 112 | LOG(FATAL) << "Unable to transition to Destructing from state " << state; 113 | } else { 114 | // NOTE: it's possible that we'll block forever if exceptions 115 | // were thrown and destruction was not successful. 116 | // if (!std::uncaught_exceptions() > 0) { 117 | WaitUntilBorrowsEquals(0); 118 | // } 119 | } 120 | } 121 | 122 | enum class State : uint8_t { 123 | Borrowing, 124 | Watching, 125 | Destructing, 126 | }; 127 | 128 | // We need to overload '<<' operator for 'State' enum class in 129 | // order to use 'CHECK_*' family macros. 130 | friend std::ostream& operator<<( 131 | std::ostream& os, 132 | const TypeErasedBorrowable::State& state) { 133 | switch (state) { 134 | case TypeErasedBorrowable::State::Borrowing: 135 | return os << "Borrowing"; 136 | case TypeErasedBorrowable::State::Watching: 137 | return os << "Watching"; 138 | case TypeErasedBorrowable::State::Destructing: 139 | return os << "Destructing"; 140 | default: 141 | LOG(FATAL) << "Unreachable"; 142 | } 143 | }; 144 | 145 | // NOTE: 'stateful_tally' ensures this is non-moveable (but still 146 | // copyable). What would it mean to be able to borrow a pointer to 147 | // something that might move!? If an implemenetation ever replaces 148 | // 'stateful_tally' with something else care will need to be taken 149 | // to ensure that 'Borrowable' doesn't become moveable. 150 | StatefulTally tally_; 151 | 152 | std::function watch_; 153 | 154 | private: 155 | // Only 'borrowed_ref/ptr' can reborrow! 156 | template 157 | friend class borrowed_ref; 158 | 159 | template 160 | friend class borrowed_ptr; 161 | 162 | template 163 | friend class borrowed_callable; 164 | 165 | void Reborrow() { 166 | auto [state, count] = tally_.Wait([](auto, size_t) { return true; }); 167 | 168 | CHECK_GT(count, 0u); 169 | 170 | do { 171 | CHECK_NE(state, State::Destructing); 172 | } while (!tally_.Increment(state)); 173 | } 174 | }; 175 | 176 | //////////////////////////////////////////////////////////////////////// 177 | 178 | template 179 | class Borrowable : public TypeErasedBorrowable { 180 | public: 181 | template < 182 | typename... Args, 183 | std::enable_if_t, int> = 0> 184 | Borrowable(Args&&... args) 185 | : TypeErasedBorrowable(), 186 | t_(std::forward(args)...) {} 187 | 188 | Borrowable(const Borrowable& that) 189 | : TypeErasedBorrowable(that), 190 | t_(that.t_) {} 191 | 192 | Borrowable(Borrowable&& that) 193 | : TypeErasedBorrowable(std::move(that)), 194 | t_(std::move(that.t_)) {} 195 | 196 | borrowed_ref Borrow() { 197 | auto state = State::Borrowing; 198 | if (tally_.Increment(state)) { 199 | return borrowed_ref(*this, t_); 200 | } else { 201 | // Why are you borrowing when you shouldn't be? 202 | LOG(FATAL) << "Attempting to borrow in state " << state; 203 | } 204 | } 205 | 206 | template 207 | borrowed_callable Borrow(F&& f) { 208 | auto state = State::Borrowing; 209 | if (tally_.Increment(state)) { 210 | return borrowed_callable(std::forward(f), this); 211 | } else { 212 | // Why are you borrowing when you shouldn't be? 213 | LOG(FATAL) << "Attempting to borrow in state " << state; 214 | } 215 | } 216 | 217 | T* get() { 218 | return &t_; 219 | } 220 | 221 | const T* get() const { 222 | return &t_; 223 | } 224 | 225 | T* operator->() { 226 | return get(); 227 | } 228 | 229 | const T* operator->() const { 230 | return get(); 231 | } 232 | 233 | T& operator*() { 234 | return t_; 235 | } 236 | 237 | const T& operator*() const { 238 | return t_; 239 | } 240 | 241 | private: 242 | T t_; 243 | }; 244 | 245 | //////////////////////////////////////////////////////////////////////// 246 | 247 | template 248 | class enable_borrowable_from_this : public TypeErasedBorrowable { 249 | public: 250 | borrowed_ref Borrow() { 251 | static_assert( 252 | std::is_base_of_v, T>, 253 | "Type 'T' must derive from 'stout::enable_borrowable_from_this'"); 254 | 255 | auto state = State::Borrowing; 256 | if (tally_.Increment(state)) { 257 | return borrowed_ref(*this, *static_cast(this)); 258 | } else { 259 | // Why are you borrowing when you shouldn't be? 260 | LOG(FATAL) << "Attempting to borrow in state " << state; 261 | } 262 | } 263 | 264 | template 265 | borrowed_callable Borrow(F&& f) { 266 | static_assert( 267 | std::is_base_of_v, T>, 268 | "Type 'T' must derive from 'stout::enable_borrowable_from_this'"); 269 | 270 | auto state = State::Borrowing; 271 | if (tally_.Increment(state)) { 272 | return borrowed_callable(std::forward(f), this); 273 | } else { 274 | // Why are you borrowing when you shouldn't be? 275 | LOG(FATAL) << "Attempting to borrow in state " << state; 276 | } 277 | } 278 | }; 279 | 280 | //////////////////////////////////////////////////////////////////////// 281 | 282 | // Represents a borrowed reference to some borrowable of type 283 | // 'T'. Unlike 'borrowed_ptr' a 'borrowed_ref' acts like a raw 284 | // reference which is always non-null. Of course, if you move a 285 | // 'borrowed_ref' then you'll get a runtime error if you attempt to 286 | // "use after move" which is strictly safer than if you only used raw 287 | // references, however, it also means that there is a set of possible 288 | // patterns that are not expressible, in particular, you might be able 289 | // to move a raw reference and still use that reference to point to 290 | // allocated memory, but your mileage may vary depending on whether or 291 | // not that is safe, and hence most of the time you always want to 292 | // treat "use after move" as an error (which is what the clang-tidy 293 | // check does as well). 294 | template 295 | class borrowed_ref final { 296 | public: 297 | // Deleted copy constructor to force use of 'reborrow()' which makes 298 | // the copying more explicit! 299 | borrowed_ref(const borrowed_ref& that) = delete; 300 | 301 | borrowed_ref(borrowed_ref&& that) { 302 | std::swap(borrowable_, CHECK_NOTNULL(that.borrowable_)); 303 | std::swap(t_, CHECK_NOTNULL(that.t_)); 304 | } 305 | 306 | ~borrowed_ref() { 307 | // May have been moved! 308 | if (borrowable_ != nullptr) { 309 | borrowable_->Relinquish(); 310 | } 311 | } 312 | 313 | borrowed_ref& operator=(borrowed_ref&& that) { 314 | std::swap(borrowable_, CHECK_NOTNULL(that.borrowable_)); 315 | std::swap(t_, CHECK_NOTNULL(that.t_)); 316 | return *this; 317 | } 318 | 319 | template < 320 | typename U, 321 | std::enable_if_t< 322 | std::conjunction_v< 323 | std::negation>, 324 | std::negation>, 325 | std::is_convertible>, 326 | int> = 0> 327 | operator borrowed_ref() const& { 328 | CHECK_NOTNULL(borrowable_)->Reborrow(); 329 | return borrowed_ref(*CHECK_NOTNULL(borrowable_), *CHECK_NOTNULL(t_)); 330 | } 331 | 332 | template < 333 | typename U, 334 | std::enable_if_t< 335 | std::conjunction_v< 336 | std::negation>, 337 | std::negation>, 338 | std::is_convertible>, 339 | int> = 0> 340 | operator borrowed_ref() & { 341 | CHECK_NOTNULL(borrowable_)->Reborrow(); 342 | return borrowed_ref(*CHECK_NOTNULL(borrowable_), *CHECK_NOTNULL(t_)); 343 | } 344 | 345 | template < 346 | typename U, 347 | std::enable_if_t< 348 | std::conjunction_v< 349 | std::negation>, 350 | std::negation>, 351 | std::is_convertible>, 352 | int> = 0> 353 | operator borrowed_ref() && { 354 | // Don't reborrow since we're being moved! 355 | TypeErasedBorrowable* borrowable = nullptr; 356 | T* t = nullptr; 357 | std::swap(borrowable, borrowable_); 358 | std::swap(t, t_); 359 | return borrowed_ptr(borrowable, t); 360 | } 361 | 362 | template < 363 | typename U, 364 | std::enable_if_t< 365 | std::conjunction_v< 366 | std::negation>, 367 | std::negation>, 368 | std::is_convertible>, 369 | int> = 0> 370 | operator borrowed_ptr() const& { 371 | CHECK_NOTNULL(borrowable_)->Reborrow(); 372 | return borrowed_ptr(CHECK_NOTNULL(borrowable_), CHECK_NOTNULL(t_)); 373 | } 374 | 375 | template < 376 | typename U, 377 | std::enable_if_t< 378 | std::conjunction_v< 379 | std::negation>, 380 | std::negation>, 381 | std::is_convertible>, 382 | int> = 0> 383 | operator borrowed_ptr() & { 384 | CHECK_NOTNULL(borrowable_)->Reborrow(); 385 | return borrowed_ptr(CHECK_NOTNULL(borrowable_), CHECK_NOTNULL(t_)); 386 | } 387 | 388 | template < 389 | typename U, 390 | std::enable_if_t< 391 | std::conjunction_v< 392 | std::negation>, 393 | std::negation>, 394 | std::is_convertible>, 395 | int> = 0> 396 | operator borrowed_ptr() && { 397 | // Don't reborrow since we're being moved! 398 | TypeErasedBorrowable* borrowable = nullptr; 399 | T* t = nullptr; 400 | std::swap(borrowable, borrowable_); 401 | std::swap(t, t_); 402 | return borrowed_ptr(borrowable, t); 403 | } 404 | 405 | borrowed_ref reborrow() const { 406 | CHECK_NOTNULL(borrowable_)->Reborrow(); 407 | return borrowed_ref(*CHECK_NOTNULL(borrowable_), *CHECK_NOTNULL(t_)); 408 | } 409 | 410 | T* get() const { 411 | return CHECK_NOTNULL(t_); 412 | } 413 | 414 | T* operator->() const { 415 | return get(); 416 | } 417 | 418 | T& operator*() const { 419 | return *get(); 420 | } 421 | 422 | // TODO(benh): operator[] 423 | 424 | template 425 | friend H AbslHashValue(H h, const borrowed_ref& that) { 426 | return H::combine(std::move(h), &that.t_); 427 | } 428 | 429 | private: 430 | template 431 | friend class borrowed_ref; 432 | 433 | template 434 | friend class borrowed_ptr; 435 | 436 | template 437 | friend class Borrowable; 438 | 439 | template 440 | friend class enable_borrowable_from_this; 441 | 442 | borrowed_ref(TypeErasedBorrowable& borrowable, T& t) 443 | : borrowable_(&borrowable), 444 | t_(&t) {} 445 | 446 | TypeErasedBorrowable* borrowable_ = nullptr; 447 | T* t_ = nullptr; 448 | }; 449 | 450 | //////////////////////////////////////////////////////////////////////// 451 | 452 | // Like 'borrowed_ref' except similar to a raw pointer (and 453 | // 'std::unique_ptr') it can be a 'nullptr', for example, by 454 | // constructing a 'borrowed_ptr' with the default constructor or after 455 | // calling 'relinquish()'. 456 | template 457 | class borrowed_ptr final { 458 | public: 459 | borrowed_ptr() {} 460 | 461 | // Deleted copy constructor to force use of 'reborrow()' which makes 462 | // the copying more explicit! 463 | borrowed_ptr(const borrowed_ptr& that) = delete; 464 | 465 | borrowed_ptr(borrowed_ptr&& that) { 466 | std::swap(borrowable_, that.borrowable_); 467 | std::swap(t_, that.t_); 468 | } 469 | 470 | ~borrowed_ptr() { 471 | relinquish(); 472 | } 473 | 474 | borrowed_ptr& operator=(borrowed_ptr&& that) { 475 | std::swap(borrowable_, that.borrowable_); 476 | std::swap(t_, that.t_); 477 | return *this; 478 | } 479 | 480 | explicit operator bool() const { 481 | return borrowable_ != nullptr; 482 | } 483 | 484 | template < 485 | typename U, 486 | std::enable_if_t< 487 | std::conjunction_v< 488 | std::negation>, 489 | std::negation>, 490 | std::is_convertible>, 491 | int> = 0> 492 | operator borrowed_ptr() const& { 493 | if (borrowable_ != nullptr) { 494 | borrowable_->Reborrow(); 495 | return borrowed_ptr(borrowable_, t_); 496 | } else { 497 | return borrowed_ptr(); 498 | } 499 | } 500 | 501 | template < 502 | typename U, 503 | std::enable_if_t< 504 | std::conjunction_v< 505 | std::negation>, 506 | std::negation>, 507 | std::is_convertible>, 508 | int> = 0> 509 | operator borrowed_ptr() & { 510 | if (borrowable_ != nullptr) { 511 | borrowable_->Reborrow(); 512 | return borrowed_ptr(borrowable_, t_); 513 | } else { 514 | return borrowed_ptr(); 515 | } 516 | } 517 | 518 | template < 519 | typename U, 520 | std::enable_if_t< 521 | std::conjunction_v< 522 | std::negation>, 523 | std::negation>, 524 | std::is_convertible>, 525 | int> = 0> 526 | operator borrowed_ptr() && { 527 | // Don't reborrow since we're being moved! 528 | TypeErasedBorrowable* borrowable = nullptr; 529 | T* t = nullptr; 530 | std::swap(borrowable, borrowable_); 531 | std::swap(t, t_); 532 | return borrowed_ptr(borrowable, t); 533 | } 534 | 535 | // 'reference()' are a set of helper(s) that return a 536 | // 'borrowed_ref' after ensuring borrowable is non-null. 537 | borrowed_ref reference() const& { 538 | CHECK_NOTNULL(borrowable_)->Reborrow(); 539 | return borrowed_ref(*CHECK_NOTNULL(borrowable_), *CHECK_NOTNULL(t_)); 540 | } 541 | 542 | borrowed_ref reference() & { 543 | CHECK_NOTNULL(borrowable_)->Reborrow(); 544 | return borrowed_ref(*CHECK_NOTNULL(borrowable_), *CHECK_NOTNULL(t_)); 545 | } 546 | 547 | borrowed_ref reference() && { 548 | // Don't reborrow since we're being moved! 549 | TypeErasedBorrowable* borrowable = nullptr; 550 | T* t = nullptr; 551 | std::swap(borrowable, CHECK_NOTNULL(borrowable_)); 552 | std::swap(t, CHECK_NOTNULL(t_)); 553 | return borrowed_ref(*CHECK_NOTNULL(borrowable), *CHECK_NOTNULL(t)); 554 | } 555 | 556 | borrowed_ptr reborrow() const { 557 | if (borrowable_ != nullptr) { 558 | borrowable_->Reborrow(); 559 | return borrowed_ptr(borrowable_, t_); 560 | } else { 561 | return borrowed_ptr(); 562 | } 563 | } 564 | 565 | void relinquish() { 566 | if (borrowable_ != nullptr) { 567 | borrowable_->Relinquish(); 568 | borrowable_ = nullptr; 569 | t_ = nullptr; 570 | } 571 | } 572 | 573 | T* get() const { 574 | return t_; 575 | } 576 | 577 | T* operator->() const { 578 | return get(); 579 | } 580 | 581 | T& operator*() const { 582 | // NOTE: just like with 'std::unique_ptr' the behavior is 583 | // undefined if 'get() == nullptr'. 584 | return *get(); 585 | } 586 | 587 | // TODO(benh): operator[] 588 | 589 | template 590 | friend H AbslHashValue(H h, const borrowed_ptr& that) { 591 | return H::combine(std::move(h), that.t_); 592 | } 593 | 594 | private: 595 | template 596 | friend class borrowed_ptr; 597 | 598 | template 599 | friend class borrowed_ref; 600 | 601 | template 602 | friend class Borrowable; 603 | 604 | borrowed_ptr(TypeErasedBorrowable* borrowable, T* t) 605 | : borrowable_(borrowable), 606 | t_(t) {} 607 | 608 | TypeErasedBorrowable* borrowable_ = nullptr; 609 | T* t_ = nullptr; 610 | }; 611 | 612 | //////////////////////////////////////////////////////////////////////// 613 | 614 | // Helper type that is callable and handles ensuring a 'borrowed_ptr' 615 | // is borrowed until the callable is destructed. 616 | template 617 | class borrowed_callable final { 618 | public: 619 | borrowed_callable(F f, TypeErasedBorrowable* borrowable) 620 | : f_(std::move(f)), 621 | borrowable_(CHECK_NOTNULL(borrowable)) {} 622 | 623 | borrowed_callable(const borrowed_callable& that) 624 | : f_(that.f_), 625 | borrowable_([&]() -> TypeErasedBorrowable* { 626 | if (that.borrowable_ != nullptr) { 627 | that.borrowable_->Reborrow(); 628 | return that.borrowable_; 629 | } else { 630 | return nullptr; 631 | } 632 | }()) {} 633 | 634 | borrowed_callable(borrowed_callable&& that) 635 | : f_(std::move(that.f_)) { 636 | std::swap(borrowable_, that.borrowable_); 637 | } 638 | 639 | ~borrowed_callable() { 640 | if (borrowable_ != nullptr) { 641 | borrowable_->Relinquish(); 642 | } 643 | } 644 | 645 | template 646 | decltype(auto) operator()(Args&&... args) const& { 647 | return f_(std::forward(args)...); 648 | } 649 | 650 | template 651 | decltype(auto) operator()(Args&&... args) & { 652 | return f_(std::forward(args)...); 653 | } 654 | 655 | template 656 | decltype(auto) operator()(Args&&... args) && { 657 | return std::move(f_)(std::forward(args)...); 658 | } 659 | 660 | private: 661 | F f_; 662 | TypeErasedBorrowable* borrowable_ = nullptr; 663 | }; 664 | 665 | //////////////////////////////////////////////////////////////////////// 666 | 667 | } // namespace stout 668 | 669 | //////////////////////////////////////////////////////////////////////// 670 | --------------------------------------------------------------------------------