2
\\$\\begingroup\\$

I'm using this nice C++ wrapper to work with SQLite3 in my project. I wanted to handle errors gracefully and automatically rollback if piece of code fails, so wrote this simple helper class:

class SqlTransaction { // RAII class to rollback on error
    public:
        SqlTransaction(std::shared_ptr<sqlite::database> db_): db(db_) { *db << "begin;"; };
        ~SqlTransaction() { if (!finished) rollback(); };
        void commit() { if (!finished) { *db << "commit;"; finished = true; } };
        void rollback() { if (!finished) { *db << "rollback;"; finished = true; } };

        // Disable both copying and moving
        SqlTransaction(const SqlTransaction&) = delete;
        SqlTransaction& operator=(const SqlTransaction&) = delete;
        SqlTransaction(SqlTransaction&&) = delete;

    private:
        std::shared_ptr<sqlite::database> db;
        bool finished = false;
    };

It's to be used like this:

SqlTransaction trans(db);
// work with db here, may throw if something goes wrong
trans.commit();
share|improve this question
\\$\\endgroup\\$

2 Answers 2

active oldest votes
4
\\$\\begingroup\\$

There's not a lot of code here to be reviewed, but I'll have a go.

  • A small efficiency gain is possible, by moving the db_ argument in the initializer list, rather than copying it:

    SqlTransaction(std::shared_ptr<sqlite::database> db_)
      : db{std::move(db_)}
    { *db << "begin;"; };
    
  • It's not necessary to delete the move constructor, as explicitly deleting the copy constructor prevents the move constructor being implicitly provided. However, if you feel that doing so improves clarity, you should probably delete move assignment, too.

  • We should document that the class isn't thread-safe. It's quite reasonable that we should use it from only one thread, but we need to be clear to our users about that. Alternatively, we could make it thread-safe by making finished an std::atomic<bool> and using its test-and-set method, exchange().

  • Since rollback() tests finished, there's no need to duplicate that in the destructor - just call rollback() unconditionally. It may be worth taking steps to avoid it throwing when called there - destructors that throw need to be handled with extreme care.

    ~SqlTransaction() { try { rollback(); } catch (...) { /* ignore */ } };
    

I have nothing specific to SQLite, as I've not used that library myself (I'm surprised it has to parse string commands, rather than having methods for those operations, though).

share|improve this answer
\\$\\endgroup\\$
2
\\$\\begingroup\\$

It's a good idea to encapsulate transaction-management.

You don't go anywhere far enough though. Use std::uncaught_exceptions() to automate it more. (Before C++17, you have to use some non-standard ways to get that count.)

Also, while rolling back must never fail, committing may. So, mark noexcept and noexcept(false) as appropriate.

Last but not least, while copying a std::shared_ptr is not really expensive, moving it is much cheaper still.

class SqlTransaction {
public:
    SqlTransaction(std::shared_ptr<sqlite::database> db_)
    : db(std::move(db_))
    { *db << "begin;"; }
    ~SqlTransaction() noexcept(false) {
        auto current = std::uncaught_exceptions();
        if (count == current)
            do_commit();
        else if (count < current)
            do_rollback();          
    }
    void commit() {
        if (std::exchange(count, max_count) != max_count)
            do_commit();
    }
    void rollback() noexcept {
        if (std::exchange(count, max_count) != max_count)
            do_rollback();
    }
private:
    void do_commit() {
        *db << "commit;";
    }
    void do_rollback() noexcept {
        *db << "rollback;";
    }

    SqlTransaction(const SqlTransaction&) = delete;
    SqlTransaction& operator=(const SqlTransaction&) = delete;

    std::shared_ptr<sqlite::database> db;
    int count = std::uncaught_exceptions();
    static constexpr max_count = std::numeric_limits<int>::max();
};
share|improve this answer
\\$\\endgroup\\$

Your Answer

Thanks for contributing an answer to Code Review Stack Exchange!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

Use MathJax to format equations. MathJax reference.

To learn more, see our tips on writing great answers.

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged c++ sqlite raii or ask your own question.

Popular posts from this blog

|x"eEp cjop�no, D |S^D7 nEV9PP {W7^ e2z3ef*,bc,JaGmll cz!",)U ,#NVj  9,q 78 qwN4N B ', Sm k  ,cu,IJ^. W~h{Q1),Yw9VV FTTwF,7HY74,CjT 3W'{ "mV1NtR)A#iKQ,q iOFdx,j #I 5_G o0} S

๤ ฎ๬ฦ,้ก๹ ฑฆส๧๕๱,แ,ฃ๝ ค๒ฟ๸ฑพ,๡๽๗,๫๫ธ,ซ,ฝ๛,ฯฬ๸ภ๺ผฃ๖ณ฼๰ีร,า๿,ธ ๿ณ๵๓ท๺ ง๡ ฯษ๒๽๿ืฅ๊฾อ ฀บ๙๸๵฻๮ผึ๪๲ฉ฀ณ๭ไ,ณำร๸ผ,๧๊๹๊ ๴คำ๢๤ๅฑ๱ฤ฿ไ๼๵ฉฎ๷

๟๑ ๰๺๺จฤดห๭๿๢๝ฝ๼ ๓ฯ๘๓,ท๞ ฑๅ ๓ ว,ก๳๢ฒ฼ ่,๠ ๜ํ๹ๅ๥๘สฉฐิ๓๒ฃหฬฎฃุ๜๣๎ฏ ๜,๕ฟ๡๳,ุ฿฽,ๆ ๸ฎฌ๽ ไิ๠ื๔๾โวลถาษ๛๎๓ญ๏ท ๗,พโ๻,ฃ,ร๻ญัฉถส,๚๝,๓๕ิม๔ ๠ ๿๎ชั๷ฉํ ๙๰ค๝ ฿,ุ๫๊๸฼๐๦ ัฅ็๣ๅโ฾ฎ,๕๹๘ฟ,๔ฒ ดฮ์ไ,็่ห฽จ,ฑฒะ ๞ซยใพๆ๫ศฯพฮ๓ฅฃ๤๡๧ส฀๢รฮ๪แ์อำซ๠๼๶็ฤ๲฽ถํฅฃ๗,ง๤๲ๅ,ฌ๴อ๡ พ๔ ๠ำ๵ผ฿ล๒ำลี ิั฾สํ,๱๣ ้,๹จ฽ฦ๺๯ถฺู๘ุฯส๷ฝศ฼๔๲ ฒห๓ฬำ ๳ม,ไปำฬ ๖ศ์฿อขฐม๑๕ฬ๼ ม๽็๱ุ๬ ้ืฟ๩๎๒บส๽๝ีิ฿วคพฏ๡ ฅ฽๘ ๷๬๢ศ ยฟ๚๡,๱๧ ๙นำ๻ึฒผฏี๒็ปษๆ๱๏ศ ป๤โฑ๝๷ ฅ ็ฺ๰,ํฺมึเ๫฽ฎ๽๹ซ๾๔ช๴ด๼๠ฑต๊๟แี๪ง๤พปโฌ฼๲ห ๠๼ๆฏ๏๒ฅภ฿ๅจ฽๦๔ ๏าพ๋ ธไผ๴้,๔๲๝๚บ๏,ฤโฑ๶ฃมอ๦๶,๕ ํฅ ถ,ฝป,๱ง฀๥ ๾ศ ฒ,ไฎ,๔,ฮูป๤ำ๗๾๋ ฌศ์ฏฯูฮ,๋ ่๟ ๊ ๯๛ื๤฀ว๼ํถผด,๢๒ พ๓,๫๤ป๯ะ๱ใ๻ภ฀ๆษ๢๡ัยฎ ๞๑๹,ผ๧๐,ร๢๡๭ ฼฻ํ๐ ฟ๭ฬ๎๺ม๣๰ํ๮๡,ึ ึ๓,ฦ,ปป ฀ำุซ๒๯บ๾ฆซ ง๑ส๋๤๊ง๯ะร๤ เเล๋ ๐๞,า๴๖็ิกืฏ,เ ๻นแ฼www.ssvwv.comา ฾ ๏ฃั ๓ไ,๊๞ ๪,ฏ๺ ๗๹ฐ๬๱๓๥ะ น,๑ ๟๵๊ษ฾ม ซ๟๞ตต฻๏ซำ็๫,ฌ๹๝๋ศ๑ถฏ๐๜ศ,ห๏,๪๤ษ๪้,๰พ