5

I am currently trying to call a sqlite3 library function, and it expects me to pass it a sqlite3**.

Here is my current code. I have one working part, and one part that gives me an error:

sqlite3 *sqlite = m_db.get();
if (sqlite3_open(std::string(m_dbName.begin(), m_dbName.end()).c_str(), &sqlite))
{

}

if (sqlite3_open(std::string(m_dbName.begin(), m_dbName.end()).c_str(), &(m_db.get()) ))
{

}

My m_db field looks like this:

std::unique_ptr<sqlite3> m_db = nullptr;

Of the two examples I displayed, the first one is working perfectly fine. However, the second gives me this error. Note that this is coming from the &(m_db.get()) part:

“Address expression must be an lvalue or a function designator”

I read up a little bit about lvalues and rvalues, but I can't seem to figure out why this syntax would not be possible. As far as I understood by now, the problem is that the return value of the .get() operation is merely only a temporary expression result, and therefore doesn't have an identifiable location in memory where I could fetch the adress from.

There has to be a way to achieve this in one statement, I guess.

Can anyone explain to me why this is not working and how I can possibly fix it?

Ajay
  • 18,086
  • 12
  • 59
  • 105
Sossenbinder
  • 4,852
  • 5
  • 35
  • 78

4 Answers4

8

The & operator can only be used with an lvalue (or with a qualified id when making pointers-to-member). The expression m_db.get() is an rvalue because it returns a pointer by value, not by reference, so you cannot take its address.

unique_ptr provides no method for accessing the underlying pointer as a reference, you'll need to store a copy somewhere as in your first example.

user657267
  • 20,568
  • 5
  • 58
  • 77
  • And the reason it won't let you access the underlying pointer is that `unique_ptr` has to do some work if you assign a new value (specifically, call the deleter on the old value). It couldn't do that work if you just go in under the covers and assign to the underlying pointer. – Steve Jessop May 27 '16 at 09:17
3

A smart pointer stores a pointer and returns it on get. What you want to do here is the opposite: you get a pointer from sqlite3_open and want to store it in a smart pointer. So you would do something like

sqlite3* db = nullptr;
sqlite3_open(..., &db);
m_db.reset(db);

As the main feature of the unique_ptr is to delete the contained pointer in its destructor, I'm not sure if it makes sense to use it here. As far as I understand it, you are supposed to call sqlite3_close on the returned pointer, not delete it.

Karsten Koop
  • 2,475
  • 1
  • 18
  • 23
  • 1
    You can give [custom deleters](http://stackoverflow.com/questions/19053351/how-do-i-use-a-custom-deleter-with-a-stdunique-ptr-member) to the `unique_ptr`. that will call a different function instead of `delete`. (But it's probably better not to use unique_ptr at all here, as you say). – M.M May 27 '16 at 08:30
  • Yes, that's true. I already changed that as soon as I found out about how it works with sqlite3. I wanted to ask the question nevertheless though, never hurts to learn something more than you need. – Sossenbinder May 27 '16 at 08:36
1

There has to be a way to achieve this in one statement, I guess.

I'm not quite sure about that; the point about temporary values really might be that it takes a statement to get a permanent one.

Also, you're messing with the semantics of the smart pointer, which you shouldn't do – .get should really not be used here.

Soooo, what I'd do is rely on C++ scoping here, and don't care about the fact that I declare a "normal" pointer first to make a smart pointer later.

your_class::initialize_db() {
    sqlite3 *sqlite;
    int retval = sqlite3_open(std::string(m_dbName.begin(), m_dbName.end()).c_str(), &sqlite);
    if(retval == SQLITE_OK) 
        m_db = std::unique_ptr<sqlite3>(sqlite);

}
Marcus Müller
  • 34,677
  • 4
  • 53
  • 94
  • Note that sqlite should be closed even if open failed: `"Whether or not an error occurs when it is opened, resources associated with the database connection handle should be released by passing it to sqlite3_close() when it is no longer required."`, so should create the unique_ptr regardless. ( But it also needs a custom deleter as mentioned elsewhere. ) – Zitrax Aug 12 '16 at 12:00
1

An lvalue is basically something which can appear on the left hand side of the assignment operator. So the error says that you can only retrieve the address of something which can be assigned to, or a function. It would have worked if you had member access to the sqlite3* pointer inside the unique_ptr, but you don't, and for good reason.

More to the point, you should not use a smart pointer in this case. If sqlite3_open requires an sqlite3** argument, then it means that the function will provide a value for the sqlite3* pointer. Basically it is an out parameter form C# or other such languages. It would have been clearer for it to be provided as a function result, but that was taken away by the result code. This is all well and good, but the smart pointer wants to have control over this value. You set the value once at initialization, but after that, the smart pointer takes care of it. And it needs to do this in order to maintain its constraints: uniqueness of ownership, deallocation when the pointer itself goes out-of-scope etc. If you basically go and overwrite the sqlite3* pointer inside, then that can't happen anymore, because the smart pointer has no way of intercepting the overwrite and deallocating the object it is currently using.

Horia Coman
  • 8,681
  • 2
  • 23
  • 25