tags:

views:

197

answers:

6

Sorry for this stupid question, but I can't find an answer by myself, I'm too new in C++ :(

class DBObject : public QObject
{
    ...
protected:
    virtual QString tableName() = 0;
};

class DBUserObject : public DBObject
{
    ...
protected:
    virtual QString tableName()  { return "profiles"; };
};

And I have this code in parent:

DBObject::DBObject(quint32 id)
    : QObject(0)
{
    ...    

    if (id != 0)
        load(id);
}

bool DBObject::load(quint32 id)
{
    QString query = QString("select %1 from %2 where id = :id")
        .arg(fieldList().join(","))
        .arg(tableName());              <--- here is trouble
    ...
}

So I'm trying to execute:

DBUserObject user(3);

But in result I have a runtime error. Why not "profiles"?

A: 

You shouldn't inline virtual functions, because some compilers can't handle that very well.

You should move the implementation of DBObject::tableName() and DBUserObject::tableName to the .cpp file.

Philippe Leybaert
The same result :(
silent
A: 

You don't seem to be doing anything wrong here. Are you sure the problem is not with your QString::arg(...) method?

Explicitly call this->tableName(); looks like a compiler problem.

-- UPDATE --

Actually your definition of tableName() should be

virtual void tableName() const { ... }

Make sure that your operator= for QString is in order (both a const and non-const version), it could be that the QString that is returned from tableName() is a temporary via the stack, in which case the operator= will be called...

Adrian Regan
Tried to do just QString tb = tableName() - the same empty string.
silent
"this" doesn't works, "const" doesn't work too :(
silent
make sure that the base and derived are defined by const otherwise the dervived does not override the base. I think that you need to look at the QString class.
Adrian Regan
+3  A: 

The problem is most probably not in the code you provided. Are you slicing the DBObjects? That could happen if you pass by value into a function, or if you store inside a container directly (not through a pointer).

Another thing is why is the tableName() not pure-virtual in your base class?

David Rodríguez - dribeas
What is pure-virtual function? Sorry again - I'm too new in C++
silent
@silent: Pure virtual means that the function does not define a body. This makes the class "abstract" (although this is not a C++ keyword). What it means is, that the class cannot be instantiated, it merely serves as a template from which other classes will be derived, which will then provide a body for the pure virtual function. It is defined like this: virtual void func() = 0;
PeterK
Thank you for the explanation, i'll remember it
silent
+2  A: 

you could use a 'pure virtual' function to make sure that only subclasses can be used, since the base class (DbObject) doesn't have a table name.

this would force all instantiations of DbObject (through the inherited classes) to have a valid table name

example: virtual QString tableName() = 0;

ianmac45
I get a runtime error on QString tb = tableName();
silent
Then your problem is that you are not actually overriding the `tableName` function in the `DBUserObject` class. Are you *sure* that what you posted above is an exact reflection of your code? Make sure that you did not misspell `tableName` or add a `const` qualifier, or change the return type, or anything of that sort.
Tyler McHenry
+14  A: 

Based on the OP's followup comment:

DBUserObject user(3). It is loading item in its constructor.

If you mean the DBObject constructor (and not the DBUserObject constructor), then there's your problem. Virtual functions do not work inside constructors. Constructors run from the least-derived (most base) class to the most-derived (actual type) class. When a class' constructor runs, the object is only of that class' type, and nothing more derived.

In other words, when you create a DBUserObject, first the QObject constructor runs, and inside that constructor the object is only a QObect and nothing more. Then, the DBObject constructor runs, and inside that constructor the object is only a DBObject and nothing more. Finally, the DBUserObject constructor runs and the object is finally a DBUserObject.

So if you call load() inside of the DBObject constructor, the object is only a DBObject at that point and so has only the DBObject version of load. This applies similarly for any virtual function.

If you want to get the effect of calling the DBUserObject version of load(), you will need to call it from the DBUserObject constructor, or from outside the class after the object has been constructed.

More information:

Tyler McHenry
+1 for actually spotting the problem: virtual vs constructors.
Pontus Gagge
Wow. It works, thank you for you help and patience.Sorry for all, I didn't expected that problem cause of constructor use :)
silent
+1 not only for the solution but also to the Guess you have made. :)
liaK
+1  A: 

First, use Google to read about "object slicing" in C++. It's easy (but wrong) to slice an object in C++, especially for novices: slicing happens if you pass an object by value (which is usually wrong) instead of passing by reference (which is usually right), for example if you declare a parameter of type "DBObject" (wrong) instead of "DbObject&" or "const DbObject&" (right).

Second, add the following statements to your DBObject class:

class DBObject : public QObject 
{ 
    ... 
protected: 
    virtual QString tableName()  { return ""; }; 
private:
    //non-default, unimplemented copy ctor and assignment operator
    DBObject(const DBObject&);
    DBObject& operator=(const DBObject&);
}; 

Declaring non-default, unimplemented copy and assignment will cause compile-time errors whereveryou try to pass a DBObject by value: so, the third step is to fix these errors by changing the parameter types to pass by reference instead.

ChrisW
Thank you for slicing and references, I'll check it today
silent