Search code examples
c++objectencapsulation

C++ rule of three for encupsalator objects


Here is my scenario:

class Database {

    public:
        Database();
        ~Database();

        void close();
                ...

    private:

        sqlite3 *database; //SQLITE3 OBJECT
        bool isOpenDb;
        ...

};

Database::Database() {

    database = 0;
    filename = "";
    isOpenDb = false;
}

Database::~Database() {
    close();
}

void Database::close() {
    sqlite3_close(database); 
    isOpenDb = false;
}

When Database object is destroyed, I want that the sqlite3 object is closed. In this form, it seems that it can be an unsafe approach since if I copy the object and I destroy it, then the copied object is in an invalid state.

In your opinion, what is the best way to proceed? I was thinking about singleton class but I'm not sure about this.


Solution

  • Make your class noncopyable. In C++11:

    class Database {
    public:
      Database(const Database&) = delete;
      Database& operator=(const Database&) = delete;
    };
    

    In C++03:

    class Database {
    private: // inaccessible 
      Database(const Database&);
      Database& operator=(const Database&);
    };
    

    Or with Boost:

    #include <boost/noncopyable.hpp>
    class Database : private boost::noncopyable {
    };
    

    In C++11 I would also make the object Movable by giving it a MoveConstructor and Move assignment operators. There you would assign the handle (or whatever your db gives you) to the new object and use some flag in the old object to indicate that nothing needs to be closed.

    Don't forget to implement swap as well!

    class Database {
      // This might look odd, but is the standard way to do it without a namespace.
      // If you have a namespace surrounding Database, put it there.
      friend void swap(Database& a, Database& b) { /* code to swap a and b */ }
    };
    

    Also: setting some value to false in your destructor has no effect. Nothing should ever be able to see the change.

    Or using a unique_ptr/shared_ptr with a custom deleter:

    struct CloseDatabase {
      void operator()(sqlite* x) { sqlite3_close(x); }
    };
    
    typedef std::unique_ptr<sqlite3, CloseDatabase> Database;