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...
-
-
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.
-
[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.
-
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;
@ -
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.