I am writing a class which is going to connect to a database. So I have a global variable std::shared_ptr<sql::Driver> driver; because I am trying to learn how to do everything with smart pointers. But this always ends up in an problem, with every smart pointer so far, beacuse the destructor of sql::Driver is protected. So call like driver.reset( ) Always end up in problems. How do I combine smart pointers with sql::Driver?
4 Answers
In short, some classes are not intended to be managed by some external caller (i.e YOU).
Some classes are designed in a way where destruction occurs as a side effect:
void finished() {
// Do something...
delete this;
}
In other cases, these classes may be managed by some friend-class like an instance-manager or some sort. A protected destructor might imply that you'll need to extend that class to gain permission.
Along these lines, singleton classes cannot be instantiated nor stored in a smart pointer of any kind since the constructor is typically private;
class Singleton {
private:
Singleton() { //...
}
};
These types of classes are not compatible with smart pointers since it does not allow for proper reference counting.
3 Comments
sql::Driver represents a singleton you can just use naked pointers, yes.The section Connector/C++ Connecting to MySQL says that you don't have to delete driver explicitly, this is why you get error: ‘virtual sql::Driver::~Driver()’ is protected. So the solution is to use a dumb pointer for driver instead of a smart one. (Only for driver!)
And Connection, Statement and ResultSet have no problems with smart pointers:
// a dumb pointer is used here intentionally, no need to delete it
sql::Driver* driver = get_driver_instance();
// shared_ptr also works
unique_ptr<sql::Connection> con( driver->connect("tcp://127.0.0.1:3306", "root", "root") );
Note that in Connector/C++ Complete Example 1 and Connector/C++ Complete Example 2 driver is not deleted.
Comments
What you are trying to do is just plain wrong. You mention that if you were using plain pointers, you wouldn't be calling delete at all. That, together with the fact that the destructor is protected, hints to the fact that you do not have ownership of the pointer value.
Since you do not have ownership, you have nothing to do with unique_ptr and/or shared_ptr, which deal with unique and shared ownership of the value pointed to.
If you insist on going on that route, and adding considerable overhead to keep reference counts on synchronization for something you do not even need, then you can do it. Both unique_ptr and shared_ptr can take a Deleter parameter. In your case, as you already mention, it would be a no-op.
struct noop_deleter
{
void operator ()(void const* ptr) const { /*no-op*/ };
};
shared_ptr<sql::driver>{ driver_ptr, noop_deleter{} };
unique_ptr<sql::driver, noop_deleter>{ driver_ptr, noop_deleter{} };
Comments
If it's protected, it is either meant to be inherited from or you're supposed to call a static Destroy() function (or similar) for destruction. For the former, you do something like:
class MyDriver : public sql::Driver
{
public:
virtual ~MyDriver() {} // calls sql::Driver::~Driver() implicitly
// ...
};
Perhaps some concrete classes in your SQL library fulfills this for you. In that case, look for how to use them.
For a destroy function, you use a custom deleter. Assuming something like this:
namespace sql
{
class Driver
{
public:
static Driver* Create(); // factory
static void Destroy( Driver* );
// ...
protected:
Driver();
virtual ~Driver();
};
}
you would do this:
std::shared_ptr<sql::Driver> driver(
sql::Driver::Create(),
sql::Driver::Destroy );
In general, however, your destructors should either be public and virtual or private and non-virtual.
A third option is that your sql::Driver is meant to be a singleton, though the dtor should still be private there unless it's designed for inheritance.
sql::Driveris protected, how are you supposed to delete it? How would you do it with plain pointers?