try_and_catch
authorMarc Celani <marccelani@fb.com>
Sat, 3 May 2014 01:17:32 +0000 (18:17 -0700)
committerDave Watson <davejwatson@fb.com>
Tue, 20 May 2014 19:53:58 +0000 (12:53 -0700)
Summary: A helper function to do try/catch on multiple exception types and store the ressult in an exception_wrapper. The purpose of this function is to best effort mimic std::current_exception(). Unlike std::current_exception(), we need to specify the types that we expect to see. Rather than writing macros or several lines per exception type to capture the exception into an exception_wrapper, this function makes writing try/catch blocks for this purpose very easy.

Test Plan: unit test

Reviewed By: mhorowitz@fb.com

FB internal diff: D1308511

@override-unit-failures

folly/ExceptionWrapper.h
folly/test/ExceptionWrapperTest.cpp

index 9174f6f7bb44276d4618ad84ed11723e6f6756a6..23a9ac7a68dffe5eb0b4405f46ce9064fbd1ba5f 100644 (file)
@@ -130,7 +130,7 @@ class exception_wrapper {
     return std::exception_ptr();
   }
 
- private:
+ protected:
   std::shared_ptr<std::exception> item_;
   void (*throwfn_)(std::exception*);
 
@@ -146,5 +146,84 @@ exception_wrapper make_exception_wrapper(Args&&... args) {
   return ew;
 }
 
+/*
+ * try_and_catch is a simple replacement for try {} catch(){} that allows you to
+ * specify which derived exceptions you would like to catch and store in an
+ * exception_wrapper.
+ *
+ * Because we cannot build an equivalent of std::current_exception(), we need
+ * to catch every derived exception that we are interested in catching.
+ *
+ * Exceptions should be listed in the reverse order that you would write your
+ * catch statements (that is, std::exception& should be first).
+ *
+ * NOTE: Although implemented as a derived class (for syntactic delight), don't
+ * be confused - you should not pass around try_and_catch objects!
+ *
+ * Example Usage:
+ *
+ * // This catches my runtime_error and if I call throwException() on ew, it
+ * // will throw a runtime_error
+ * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
+ *   if (badThingHappens()) {
+ *     throw std::runtime_error("ZOMG!");
+ *   }
+ * });
+ *
+ * // This will catch the exception and if I call throwException() on ew, it
+ * // will throw a std::exception
+ * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
+ *   if (badThingHappens()) {
+ *     throw std::exception();
+ *   }
+ * });
+ *
+ * // This will not catch the exception and it will be thrown.
+ * auto ew = folly::try_and_catch<std::runtime_error>([=]() {
+ *   if (badThingHappens()) {
+ *     throw std::exception();
+ *   }
+ * });
+ */
+
+template <typename... Exceptions>
+class try_and_catch;
+
+template <typename LastException, typename... Exceptions>
+class try_and_catch<LastException, Exceptions...> :
+    public try_and_catch<Exceptions...> {
+ public:
+  template <typename F>
+  explicit try_and_catch(F&& fn) : Base() {
+    call_fn(fn);
+  }
+
+ protected:
+  typedef try_and_catch<Exceptions...> Base;
+
+  try_and_catch() : Base() {}
+
+  template <typename F>
+  void call_fn(F&& fn) {
+    try {
+      Base::call_fn(std::move(fn));
+    } catch (const LastException& e) {
+      this->item_ = std::make_shared<LastException>(e);
+      this->throwfn_ = folly::detail::Thrower<LastException>::doThrow;
+    }
+  }
+};
+
+template<>
+class try_and_catch<> : public exception_wrapper {
+ public:
+  try_and_catch() {}
+
+ protected:
+  template <typename F>
+  void call_fn(F&& fn) {
+    fn();
+  }
+};
 }
 #endif
index c20535eeaa937187e47ab668f8d1fa364e565d1c..27da94f9e9a01ed64dc069e0e0a3c214d549f399 100644 (file)
@@ -43,3 +43,44 @@ TEST(ExceptionWrapper, boolean) {
   ew = make_exception_wrapper<std::runtime_error>("payload");
   EXPECT_TRUE(bool(ew));
 }
+
+TEST(ExceptionWrapper, try_and_catch_test) {
+  std::string expected = "payload";
+
+  // Catch rightmost matching exception type
+  exception_wrapper ew = try_and_catch<std::exception, std::runtime_error>(
+    [=]() {
+      throw std::runtime_error(expected);
+    });
+  EXPECT_TRUE(ew.get());
+  EXPECT_EQ(ew.get()->what(), expected);
+  auto rep = dynamic_cast<std::runtime_error*>(ew.get());
+  EXPECT_TRUE(rep);
+
+  // Changing order is like catching in wrong order. Beware of this in your
+  // code.
+  auto ew2 = try_and_catch<std::runtime_error, std::exception>([=]() {
+    throw std::runtime_error(expected);
+  });
+  EXPECT_TRUE(ew2.get());
+  // We are catching a std::exception, not std::runtime_error.
+  EXPECT_NE(ew2.get()->what(), expected);
+  rep = dynamic_cast<std::runtime_error*>(ew2.get());
+  EXPECT_FALSE(rep);
+
+  // Catches even if not rightmost.
+  auto ew3 = try_and_catch<std::exception, std::runtime_error>([]() {
+    throw std::exception();
+  });
+  EXPECT_TRUE(ew3.get());
+  EXPECT_NE(ew3.get()->what(), expected);
+  rep = dynamic_cast<std::runtime_error*>(ew3.get());
+  EXPECT_FALSE(rep);
+
+  // If does not catch, throws.
+  EXPECT_THROW(
+    try_and_catch<std::runtime_error>([]() {
+      throw std::exception();
+    }),
+    std::exception);
+}