Class member: pointer or variable?



  • Hi all,
    after years of mind burned by Java development, I had a few doubts about the correct use of C++. One of these are about the difference in the following class member declaration:

    @
    class MyClass{
    QAction myAction1;
    QAction* myAction2;
    }
    @

    What are pros and cons of the above declaration? In other words, when should I use a pointer instead of a variable? I know this is a trivial question....



  • QObjects seem to generally feel most at home on the heap. The parent-child mechanism prevents many cases of memory loss because of forgotten deletes. However, the mechanism can also create some issues if you use it in conjunction with class members, strange things may happen. The parent may try to delete the child items, but they will also be deleted by the destructor itself. That is a problem. Also, you usually pass pointers to QObject in the Qt API, and in some cases the object you pass the pointer to takes ownership of the object. Doing that with members is again the same problem.

    So, for QObjects, I tend to create them on the heap. For other objects, it depends...



  • Forget my ignorance here, but if I allocate myClass on the heap, is it correct to assume that also its member are all allocated in the heap independently of how they are declared (i.e., variable or pointers) or each variable is always on the stack?



  • If you allocate your object on the heap all members (including pointers) will be allocated on the heap as well - your object is a unit and can't be split. The object your member pointer points to can be located anywhere - either on the stack or (most probably) on the heap as it is not part of the object itself.



  • It's not a trivial question. You should ask

    • whether the lifespan of your members is tied to the lifespan of the containing object: if untied, you should then allocate on heap and rely on Qt parent/child, or smart pointers, etc.
    • if your code looks good even with lots of reference operators (we're talking about QObjects, and they're always passed by pointer)

    Preventing an additional question: it's perfectly safe to have child QObjects as members.



  • [quote author="peppe" date="1317209789"]It's not a trivial question. You should ask

    • whether the lifespan of your members is tied to the lifespan of the containing object: if untied, you should then allocate on heap and rely on Qt parent/child, or smart pointers, etc.
    • if your code looks good even with lots of reference operators (we're talking about QObjects, and they're always passed by pointer)
      [/quote]
      Good advice.
      [/quote]
      Preventing an additional question: it's perfectly safe to have child QObjects as members. [/quote]
      That is only save if you do not give the member-QObject a parent, AFAIK.


  • [quote]That is only save if you do not give the member-QObject a parent, AFAIK. [/quote]

    No: it's safe because child-deletion happen in the QObject dtor. By then, your subclass dtor was already run and the subclass members destroyed (as per C++ rules). When your QObject member is destroyed, it removes itself from its parent. So no double deletion happen.



  • [quote author="Andre" date="1317210204"]
    Preventing an additional question: it's perfectly safe to have child QObjects as members. [/quote]
    That is only save if you do not give the member-QObject a parent, AFAIK.
    [/quote]

    not only parent oare a problem here, also methods, that put widgets in a layout, change responsibility etc.

    [quote author="peppe" date="1317211120"][quote]That is only save if you do not give the member-QObject a parent, AFAIK. [/quote]

    No: it's safe because child-deletion happen in the QObject dtor. By then, your subclass dtor was already run and the subclass members destroyed (as per C++ rules). When your QObject member is destroyed, it removes itself from its parent. So no double deletion happen.[/quote]

    Yes and no :-) If you put yourself as parent, you are right. If you use other objects as parent, you are not, Think of the following:

    @
    class MyClass : public QObject
    {
    private:
    QObjectDerived1 der1;
    QObjectDerived2 der2;
    QObjectDerived3 der3;
    }

    MyClass::MyClass :
    der1(this),
    der2,
    der3
    {
    der2.setParent(&der3);
    der3.setParent(&der1);
    }
    @

    This could lead top a crash, as it is not garanteed, in which order members are destructed.



  • In addtion, sometimes you are forced to use pointers, for example when implementing PIMPLs (or d-pointers as Qt calls it) in conjunction with forward declarations.



  • [quote author="Gerolf" date="1317215439"]
    This could lead top a crash, as it is not garanteed, in which order members are destructed.[/quote]

    Members are always constructed in the order they're declared (in the source code), and destroyed in the reverse order. Then you have to be careful, but this is standard C++ ruling.

    I was talking about having a member QObject as a child as well (f.i. a subwidget inside a widget).



  • [quote author="Lukas Geyer" date="1317222348"]In addtion, sometimes you are forced to use pointers, for example when implementing PIMPLs (or d-pointers as Qt calls it) in conjunction with forward declarations.[/quote]

    Forward declarations are indeed a good point.



  • I usually declare class members as pointers, as I regard those as "long living" objects. I declare method local QObject based objects - call them "short living objects" - as stack based variables from time to time, it may save you the delete if you have several exit points (return) in a method.

    But with all answers to questions like this, do not take them as rules you have to follow strictly all the time. It's a rule of thumb and with every of these advices, there are exceptions, and there are good reasons for those.

    And please, adhere to the advice mentioned earlier in this thread, that if you pass QObjects to other QObjects that may take or transfer parentship, you must be extremely carful! The safe bet is a heap based object in these cases.



  • Let's complicate a bit much, at least for me.
    What if I've got a Dialog window (therefore something that is parented) that contains a variable like a QSqlDatabase and returns the variable via a reference to a caller?
    Something like

    @class MyDialog : public QDialog{
    private:
    QSqlDatabase db;

    public:
    QSqlDatabase& database(){ return db; }

    }@

    In such case I'm going to create the dialog parenting it with my main window, or another one, like in the following:

    @
    MyDialog* dia = new MyDialog( this );
    //...
    QSqlDatabase db = dia.database();
    @

    the dialog will be deleted when the window that has created it is disposed. But if this is inserted into a slot, each time the slot is performed a new dialog will be created, so unless I keep a reference to the dialog, I have to delerte it manually:

    @
    MyDialog* dia = new MyDialog( this );
    //...
    QSqlDatabase db = dia.database();
    delete dia;
    @

    Is such deletion secure? Because I'm going to delete a dialog and its inner variable db, that is returned from the dialog itself. I guess the trick is about who creates the db instance and how it survives its caller, but I'm a bit confused.


  • Moderators

    Why not create your own QSqlDatabase instance and pass it to any instances of your dialog that need it? Sounds like that should be the proper scope of the database in your scenario.

    @
    MyDialog::MyDialog(QSqlDatabase *db_to_use, QObject *parent) : QDialog(parent) { }
    @

    Then in your other code, as appropriate:
    @
    QSqlDatabase *db = new QSqlDatabase(...);
    ...
    MyDialog *dia = new MyDialog(db, this);
    ...
    delete dia;
    ...
    // continue to use "db" as long as needed.
    ...
    delete db;
    @



  • Notice that QSqlDatabase is a value-based class, representing a connection to a database. It can safely be passed and returned by value.



  • Left aside the value base QSqlDatabase, the problem you presented is not Qt specific. You will stumble upon those questions all the time in C++. As always there is no general rule to solve this, as it depends on your objects (assignable/copyable or not), their lifetimes and others.



  • [quote author="fluca1978" date="1317200766"]Hi all,
    after years of mind burned by Java development, I had a few doubts about the correct use of C++. One of these are about the difference in the following class member declaration:

    @
    class MyClass{
    QAction myAction1;
    QAction* myAction2;
    }
    @

    What are pros and cons of the above declaration? In other words, when should I use a pointer instead of a variable? I know this is a trivial question....[/quote]

    You should use pointers where you require a level of indirection, and where a reference isn't a better option.

    Java is pass by reference, C++ is pass by value. What pointers and references allow you to do is pass the address of something around rather than that something itself, effectively giving the illusion of pass by reference. You should use a pointer or a reference wherever it makes sense to provide indirection (this is the catch all), share data across instances of one or more classes or prevent objects being copied (either where it is not economical to do so because of time or space complexity or it's just not a good ideal).

    The primary difference between a reference and a pointer is a reference gives certain guarantees, namely that it corresponds to something, that that something is valid, and that it will be valid for the lifetime of that reference. An invalid reference is bad, most compilers will happily compile that (with a warning or two) and your application will behave badly. Pointers don't place any of these guarantees, which makes them both a bit more flexible but a bit more error prone. The fact that a pointer can be null provides yet another way to write functions with 'optional' arguments, a technique used and abused almost everywhere.

    Pointers are also required for dynamic allocation, which is a must for things like dynamically sized arrays and data structures, and allocating large pieces of memory on the heap. It is not feasible to allocate massive amounts of data on the stack, because it pales in size compared to the heap. Thus, we use new and delete to allocate dynamically on the heap. This is especially important on embedded devices and micro-controllers, where memory is often scarce.

    Oh, and also, C++ doesn't have garbage collection, so once you understand pointers, a good exercise is to implement your own smart pointer and auto pointer classes using templates, because:

    To write good C++, you need to understand pointers.

    To write good C++, you need to understand templates.

    Aside from all of this, C/C++ is a bit closer to the machine than Java, and requires us as developers to understand the computer not only as an abstract device for computation, but also as a physical object. This requirement is often the basis for the perceived steep learning curve for C and C++, a often made criticism that I find absolutely abhorrent coming from software developers!

    Hope that helps.



  • Thanks for the explaination, of course I know what a pointer and a reference is since I come from C, passing then to c++ and then to Java.
    I agree with you that C++ is a much better language than Java is, and just think about the criticism about he operator overloading, that is absurd at all! Anyway, C++ can be a lot more complex than other languages, just consider how many keywords are there and, as an example, how the use of "const" depends on when it is placed in the same line.



  • [quote]Java is pass by reference[/quote]

    What has that snippet do to with Java? We're talking about having objects vs. pointers to objects as members, a concept that Java hasn't (all "objects" are actually pointers to them in Java).

    [quote]Java is pass by reference, C++ is pass by value. What pointers and references allow you to do is pass the address of something around rather than that something itself, effectively giving the illusion of pass by reference[/quote]

    NO! NO! NO! NO!

    This is just PLAIN, TOTALLY AND UTTERLY WRONG. Java always passes by value. There's no such thing as pass-by-reference in Java. "References" in Java's slang are actually pointers to objects, not aliases.

    C++ can instead pass and return by value or by reference.

    [quote]The primary difference between a reference and a pointer is a reference gives certain guarantees, namely that it corresponds to something, that that something is valid, and that it will be valid for the lifetime of that reference[/quote]

    This is just PLAIN, TOTALLY AND UTTERLY WRONG.
    @
    int & foo() {
    int a = 42;
    return a;
    }

    void bar() {
    int & r = foo();
    // oops! r is an invalid reference
    }
    @

    [quote]An invalid reference is bad, most compilers will happily compile that (with a warning or two) and your application will behave badly. [/quote]

    So first you say that references are always valid, then you say they may be invalid?

    [quote]Pointers don’t place any of these guarantees, which makes them both a bit more flexible but a bit more error prone. The fact that a pointer can be null provides yet another way to write functions with ‘optional’ arguments, a technique used and abused almost everywhere.[/quote]

    Abused in like the hundred of QObject subclasses in Qt (all of them have an optional QObject *parent argument in the ctor, defaulting to NULL)?

    (In other news, here's my opinion about pointers vs. references http://www.parashift.com/c++-faq-lite/references.html#faq-8.6 )

    [quote]# To write good C++, you need to understand templates.[/quote]

    Qt has allowed people to write good C++ for ages, without requiring anyone to master templates.



  • I want a thumbs down button. NOW. (oh, surely not because of peppes answer...)



  • [quote author="peppe" date="1317650577"]
    What has that snippet do to with Java? We're talking about having objects vs. pointers to objects as members, a concept that Java hasn't (all "objects" are actually pointers to them in Java).
    [/quote]

    Poster says he's a Java programmer. Was trying to frame the concept in a manner that would be familiar to him.

    [quote author="peppe" date="1317650577"]
    NO! NO! NO! NO!
    [/quote]

    A misunderstanding on my part, I'll concede that, thanks. Haven't touched Java in years.

    [quote author="peppe" date="1317650577"]
    This is just PLAIN, TOTALLY AND UTTERLY WRONG.
    [/quote]

    Your example is silly and shows that you've not only misunderstood what I've said, but seem to misunderstand what constitutes valid, working C++. You've created a reference to variable which immediately goes out of scope, creating an invalid reference. You've violated an axiom of valid C++, that references should be valid.

    [quote author="peppe" date="1317650577"]
    So first you say that references are always valid, then you say they may be invalid?
    [/quote]

    In terms of correct code, if you have a null reference, you have a bug. So yes, references are always valid in correct C++. If you like writing software riddled with segmentation faults, be my guest, just don't peddle it as anything but garbage. Just because something compiles, doesn't mean it will run (Do I really need to tell you this?), it's no mean feat to write garbage code that will compile and fault when run.

    [quote author="peppe" date="1317650577"]
    Abused in like the hundred of QObject subclasses in Qt (all of them have an optional QObject *parent argument in the ctor, defaulting to NULL)?
    [/quote]

    Abused in that there are a multitude of ways to provide optional arguments in C++, and often developers fall back on pointer arguments and monolithic functions as a familiar tool. Congratulations on picking out a single instance where I'd personally consider the usage well chosen, how about you go and have a look at some of the badly evolved C89 libraries, and tell me how great they are to work with?

    [quote author="peppe" date="1317650577"]
    Qt has allowed people to write good C++ for ages, without requiring anyone to master templates.
    [/quote]

    I didn't say master templates, did I? I said understand templates. Given that the standard library is built around the principle of reusable code and templates embody this principle, I think it would be reasonable to say that people should understand templates.

    The statement that good, modern C++ doesn't require an advanced understanding of templates is absolutely ridiculous, and out of step with the direction of the language of the last 2 decades. Boost, the standard library and Qt all use templates, and at some point, someone had to master them.

    When all you have is a hammer, everything looks like a nail. Good luck building a house buddy.

    Oh, and as for your opinion (or rather, that of Mr Cline), if you'd looked beyond your nose, you would have seen this:

    @// What does it mean that a reference must refer to an object, not a dereferenced NULL pointer?
    // It means this is illegal:

    T* p = NULL;
    T& r = p; ← illegal
    T
    p = NULL;
    T& r = *p; ← illegal@

    [quote author="Volker" date="1317683047"]I want a thumbs down button. NOW. (oh, surely not because of peppes answer...)[/quote]

    Directed at me?



  • [quote]You’ve created a reference to variable which immediately goes out of scope, creating an invalid reference.[/quote]

    I know. I did it on purpose. Just to show that references give no additional guarantees in that sense, compared to pointers (both can be "invalid"/"dangling"). Another example:

    @
    int main() {
    int *a = new int(42);
    int &r = *a;
    delete a;
    // oops! r is invalid now
    }
    @

    That's because you said:
    [quote]The primary difference between a reference and a pointer is a reference gives certain guarantees, namely that it corresponds to something, that that something is valid, and that it will be valid for the lifetime of that reference. An invalid reference is bad, most compilers will happily compile that (with a warning or two) and your application will behave badly.[/quote]

    Which, again: makes no sense. You can't say that references give the guarantee that it refers to something "valid" throughout its entire lifetime, because it's false. In fact you then say that invalid references can exist. So that's not what references do actually guarantee.

    The primary differences between a reference and a pointer are:

    references can't be reseated

    references don't have "special" values (NULL)

    That's all.

    [quote]In terms of correct code, if you have a null reference, you have a bug. So yes, references are always valid in correct C++. If you like writing software riddled with segmentation faults, be my guest, just don’t peddle it as anything but garbage. Just because something compiles, doesn’t mean it will run (Do I really need to tell you this?), it’s no mean feat to write garbage code that will compile and fault when run.[/quote]

    Of course. I was referring to your statement above. Obviously that's a bug (exactly like a dangling pointer, although I should check the standard to find out if the only fact of having invalid references leads to UB).

    [quote]how about you go and have a look at some of the badly evolved C89 libraries, and tell me how great they are to work with?[/quote]

    Oh, doing Xlib programming is absolutely great :-D

    [quote]
    Oh, and as for your opinion (or rather, that of Mr Cline), if you’d looked beyond your nose, you would have seen this:

    @
    // What does it mean that a reference must refer to an object, not a dereferenced NULL pointer?
    // It means this is illegal:

    T* p = NULL;
    T& r = p; ← illegal
    T
    p = NULL;
    T& r = *p; ← illegal
    @
    [/quote]

    The problem with that snippet, in my humble opinion, has little to do with references -- you're dereferencing the NULL pointer, and that's undefined behaviour in C/C++ (even before thinking of initializing a reference to whatever *p returns).

    [quote]The statement that good, modern C++ doesn’t require an advanced understanding of templates is absolutely ridiculous, and out of step with the direction of the language of the last 2 decades. Boost, the standard library and Qt all use templates, and at some point, someone had to master them.[/quote]

    I totally agree; but luckily people managed to write very solid code by using Qt and avoiding to mess up with highly templated (and possibly unreadable) code. I don't consider using QList<Foo> "understanding" templates in any way (which is unfortunate -- knowing what you're doing is always a good thing).

    So, can we consider this argument settled down? :)


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.