Resource Management the C++ Way

0cc0e5c9601b6c93466f44f942279631?s=47 Zhihao Yuan
November 08, 2012

Resource Management the C++ Way

Presented on NIU ACM. Some RAII related topics in C++.

0cc0e5c9601b6c93466f44f942279631?s=128

Zhihao Yuan

November 08, 2012
Tweet

Transcript

  1. Resource Management the C++ Way by Zhihao Yuan <lichray@gmail.com>

  2. C++11 features in this talk Null pointer constant void f(char

    *) {} void f(int) {} f(NULL); // error, ambiguous f(nullptr); // ok, the first one
  3. C++11 features in this talk Deleted definition // hide the

    default copy-ctor with private: A(A const&); // or just say A(A const&) = delete;
  4. C++11 features in this talk Type deduction from initializer //

    define an iterator with std::vector<std::pair<std::string, int>>:: iterator i = v.begin(); // or just auto i = v.begin();
  5. The definitions To a program... A resource is any virtual

    component of limited availability within an execution environment. • Memory space • Semaphore • Regular file, socket, pipe, etc.
  6. The definitions To a programmer... Resource management is to circly

    acquire, access, and release the resources. char *p = new char[len]; // acquisition cin.getline(p, len); // access cout << p << endl; delete [] p; // release
  7. The problems we face Memory leak char *p = new

    char[len]; // some operations... if (stat(p, &sb)) return; // nice shot! memory leaked. delete [] p; // not reached...
  8. The problems we face Memory leak, again char *p =

    new char[len]; // some operations... throw! if (stat(p, &sb)) { delete [] p; // before leaving the scope return; } delete [] p;
  9. The problems we face Multiple acquisitions char *p = new

    char[len]; try { auto *t1 = new T; auto *t2 = new T; // ctor throws! } catch (... /* BAD! */) { delete [] p; throw; } delete [] p;
  10. The problems we face Deadlock mutex_.lock(); // block forever if

    we forgot // to unlock it. // but too many things can throw exceptions // Throws: basic_filesystem_error<Path> boost::filesystem::copy_file("a.txt", "b.txt"); mutex_.unlock();
  11. The problems we face I like <stdio.h> too, but... FILE

    *fp = fopen("topsecret", "r+"); /* I don't think you can handle this. */ fclose(fp);
  12. The problems we face I like <stdio.h> too, but... FILE

    *fp = fopen("topsecret", "r+"); /* I don't think you can handle this, without RAII! */ fclose(fp);
  13. The C++ way Resource Acquisition Is Initialization

  14. The benefits of RAII With a simple wrapper... struct File

    { explicit File(char const* path, char const* mode = "r") { if ((fp_ = fopen(path, mode)) == nullptr) throw system_error(errno, system_category()); } ~File() { int ec = fclose(fp_); assert(ec == 0); } FILE * get() { return fp_; } File(File const&) = delete; File& operator=(File const&) = delete; private: FILE *fp_; };
  15. The benefits of RAII You can(must) forget fclose()! File f("topsecret");

    FILE *fp = f.get(); /* do anything you want except calling fclose(3) */ char buf[BUFSIZ]; while(fgets(buf, sizeof(buf), fp)) fputs(buf, stdout); if (ferror(fp)) return -1; // f.~File() called /* ... */ return 0; // f.~File() called }
  16. Behind RAII A shortcut? FILE *fp = File("topsecret").get(); File f("topsecret");

    FILE *fp = f.get();
  17. Behind RAII No! FILE *fp = File("topsecret").get(); // ~~~~~~~~~~~~~~~~~ //

    this is a temporary object, whose lifetime lasts // until the full-expression get evaluated. File f("topsecret"); // f has the auto storage, FILE *fp = f.get(); // so its lifetime lasts // until the scope exits
  18. Behind RAII Why they leak? FILE *fp = fopen(/* ...

    */); // ~~~~~~~~~~~~~~~~ // lifetime lasts until fclose() is called mutex_.lock(); // // lifetime lasts until .locked() is called char *p = new char[len]; // ~~~~~~~~~~~~~ // lifetime lasts until delete [] is called
  19. Behind RAII The semantics To associate the lifetime of a

    resource with the lifetime of an object.
  20. RAII in action Step 1: Find the the resource: mutex_.lock();

    // you Acquired the 'lock' // a 'lock' is a Resource
  21. RAII in action Step 2: Represent the resource: template <typename

    Mutex> struct Lock { explicit Lock(Mutex& m) : mutex_(m_) {} ~Lock() {} /* copy/move-ctor/opt are omitted here */ private: Mutex& mutex_; };
  22. RAII in action Step 3: Associate their lifetimes: template <typename

    Mutex> struct Lock { explicit Lock(Mutex& m) : mutex_(m_) { mutex_.lock(); } ~Lock() { mutex_.unlock(); } private: Mutex& mutex_; };
  23. RAII in action Create a handle object! { Lock<std::mutex> lock(mu_);

    // lock the whole scope /* do something requiring synchronization */ }
  24. Library solutions std::lock_guard<> { lock_guard<std::mutex> lock(mu_); /* do something requiring

    synchronization */ } std::unique_lock<> unique_lock<std::mutex> lock1(mu_); // transfer ownership return lock1;
  25. Library solutions std::unique_ptr<> unique_ptr<char[]> my_strdup(char const *str) { size_t n

    = strlen(str) + 1; unique_ptr<char[]> p(new char[n]); copy_n(str, n, p.get()); return p; } // an auto-free C-style string! auto q = my_strdup(getenv("HOME"));
  26. Library solutions unique_ptr is the new pointer struct nativebuf {

    /* unmanaged new [], delete []... */ ~nativebuf() { delete [] buf; } private: char *buf; }; struct nativebuf { /* buf.reset(new char[nlen]); */ private: unique_ptr<char[]> buf; };
  27. More behind RAII What if... unique_ptr<nativebuf> bp(new nativebuf); /* or,

    in a plain-old C way */ typedef struct { char *p; } A; A *a = malloc(sizeof(A)); a->p = malloc(100); free(a); // or free(a->p) first? free(a->p);
  28. More behind RAII The safeguard Objects are destructed in the

    reverse order of construction.
  29. More behind RAII Ordered construction struct postream : ostream➊, private

    nativebuf➋ { /* ... */ private: C member1_➌; C member2_➍; }; auto *pos = new postream[3]; // left to right delete [] pos; // right to left
  30. More behind RAII Unordered construction Non-local static/thread-local objects in different

    preprocessed files: // a.cc // b.cc A a; B b; Use the references to local objects instead: A& a() { B& b() { static A obj; static B obj; return obj; return obj; } }
  31. More behind RAII Unordered evaluation Indeterminately sequenced creation of temporaries:

    // void f(unique_ptr<A> const&, unique_ptr<B> const&); f(unique_ptr<A>(new A), unique_ptr<B>(new B)); // A possible sequence: // new A, new B throws, "new A" leaked. Name them: unique_ptr<A> a(new A); unique_ptr<B> b(new B); f(a, b);
  32. RAII made easy An "innovation" from Go /* http://blog.golang.org/2010/08/defer-panic-and-recover.html */

    src, err := os.Open(srcName) if err != nil { return } defer src.Close() dst, err := os.Create(dstName) if err != nil { return } defer dst.Close()
  33. RAII made easy "defer" in C++ /* https://github.com/lichray/deferxx */ FILE

    *src = fopen(srcName, "r"); if (src == nullptr) { return -1; } defer(fclose(src)); FILE *dst = fopen(dstName, "wx"); if (dst == nullptr) { return -1; } defer(fclose(dst));
  34. Summary Personal suggestions • Prefer the library-provided RAII solutions. •

    defer (ScopeGuard) is a bottom line. • Write your own RAII wrappers for reuse. • Always name the handle objects.
  35. Summary Don’t saw by hand when you have power tools:

    C++’s “resource acquisition is initialization” (RAII) idiom is the power tool for correct resource handling. RAII allows the compiler to provide strong and automated guarantees that in other languages require fragile hand- coded idioms. When allocating a raw resource, immediately pass it to an owning object. Never allocate more than one resource in a single statement. - H. Sutter & A. Alexandrescu
  36. Further reading For the working programmers • Reference counted std::shared_ptr<>

    http://www.boost.org/doc/libs/1_51_0/libs/smart_ptr/shared_ptr. htm#BestPractices • Deadlock avoiding std::lock<> http://www.justsoftwaresolutions.co. uk/threading/multithreading-in-c++0x-part-7-locking-multiple- mutexes.html • Exception safety http://www.gotw.ca/gotw/082.htm
  37. Questions?

  38. References Alexandrescu, Andrei and Petru Marginean. "Generic<Programming>: Change the Way

    You Write Exception-Safe Code -- Forever." Dr.Dobb's. Web. 1 Dec. 2000. <http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758> Eckel, Bruce, Chuck D Allison, and Chuck Allison. Thinking In C++. 2nd ed. Vol. 2. Upper Saddle River, NJ: Pearson Prentice Hall, 2004. 151. Print. Liu, Weipeng. "Modern C++ Practices." Home page. Web. 27 Aug. 2012. <http://mindhacks.cn/2012/08/27/modern-cpp-practices/> Meyers, Scott Douglas. "Item 4: Make sure that objects are initialized before they're used." Effective C++: 55 Specific Ways To Improve Your Programs And Designs. 3rd ed. Pearson Education, 2005. Google Books. Web. Sutter, Herb. "GotW #104: Smart Pointers, Part 2." Home page. Web. 5 June 2012. <http://herbsutter.com/gotw/_104/> Sutter, Herb, and Andrei Alexandrescu. "13. Ensure resources are owned by objects. Use explicit RAII and smart pointers." C++ Coding Standards: 101 Rules, Guidelines, And Best Practices. Pearson Education, 2005. Google Books. Web. Toit, Stefanus Du. Working Draft, Standard for Programming Language C++. N3337 ed. ISO/IEC JTC1/SC22/WG21 - The C++ Standards Committee. Web. 16 Jan. 2012. 3.6.2/2, 3.7.3, 4/1, 4.10/1, 4.12, 5.3.5/6, 7.1.6.4, 8.4.3, 12.1/11, 12.2/3. <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf>