Basic C++ question



  • Hi there,

    I recently switched from Java to QT and i'm having trouble creating simple classes hierarchy.

    Here is the class schema I want to represent :
    https://www.dropbox.com/s/crjsp4sx4wx5eei/model.png
    (A workout contain one or more Interval)

    WORKOUT.H :
    @#ifndef WORKOUT_H
    #define WORKOUT_H

    #include <QString>
    #include <QList>
    #include "interval.h"

    class Workout
    {

    public:
    enum Type
    {
    Tempo,
    Endurance
    };

    Workout();
    Workout(QList<Interval> lstInterval, QString name, QString createdBy, Type);
    
    QList<Interval> getLstInterval();
    QString getName();
    QString getCreatedBy();
    QString getTypeToString();
    Type getType();
    int getMaxPower();
    int getNbInterval();
    double getTotalLength();
    

    private :

    QList<Interval> lstInterval;
    QString name;
    QString createBy;
    Type type;
    int maxPower;
    double totalLength;
    

    };

    #endif // WORKOUT_H@

    INTERVAL.H:
    @#ifndef INTERVAL_H
    #define INTERVAL_H

    class Interval
    {
    public:

    Interval(double length, int targetPourcentFTP, int targetHR);
    
    double getLength();
    int getTargetPourcentFTP();
    int getTargetHR();
    

    private:

    double length;
    int targetPourcentFTP;
    int targetHR;
    

    };

    #endif // INTERVAL_H@

    How I use both classes:
    @ QList<Interval> lstInterval;

    Interval interval(5, 100, 160);
    Interval interval2(1, 80, 160);
    lstInterval.append(interval);
    lstInterval.append(interval2);
    
    Workout work(lstInterval, "name", "me", Workout::Endurance);
    
    qDebug() << "Bug at this point";
    
    workoutDialog = new WorkoutDialog(this);
    workoutDialog->initGraph(work);
    workoutDialog->setModal(true);
    workoutDialog->show();@
    

    I read the best practice around the net, i.e : http://stackoverflow.com/questions/2497541/c-best-practice-returning-reference-vs-object

    But I find a lot of different opinion on the subject, I just want the easiest way to build the program, and easy to maintain, with pointers it seems really hard to debug. So I figured I would stay away from them. Also the key word "new" in C++ seems to be avoided in general for some reason, that's why I didn't use it in the code below, as it seem you need to use to create a pointer to use it.

    If you can give me an hand, would be appreciated, I wish I could learn C++ easily!


  • Moderators

    Probably one thing you should have a look at are some tutorials "e.g. this one":http://www.cplusplus.com/doc/tutorial/

    I do not know anything about java, so there is no chance that I can help you with differences between java and C++.

    The debate over using references and pointers is endless and pointless in my opinion. Basically you can choose what is more comfortable for you.

    Pointers and references act similar in parameter lists. Instead of placing the whole object on stack both are handling only the address.

    Using
    @
    void foo ( classObj &obj )
    {
    obj.setSomething( 10 );
    }
    @

    or
    @
    void foo ( classObj *obj )
    {
    obj->setSomething( 10 );
    }
    @

    really depends on what you like more for such a case. The major advantage comes when you do not want that the function is able to change the contents of your object.

    If you write this
    @
    void foo ( const classObj &obj )
    {
    obj.setSomething( 10 );
    }
    @

    the compiler will spit out an error message. So, sometimes the reference is giving you some extra comfort, when you want to make sure that something is not changed.

    The new operator is the method to allocate dynamically memory. And you typically have to use it very often.
    @
    classObj *Ptr; // this creates the pointer
    Ptr = new classObj; // this allocate the memory and assigns it to the
    // pointer
    @

    The tricky thing is not to loose the pointer with the memory still allocated. The resulting memory leak may haunt you forever. Therefore, you need to pay attention when and where you allocate memory and what you do with the pointer.

    @
    void foo()
    {
    classObj *Ptr = new classObj;
    Ptr->setSomething ( 10 );
    }
    @
    The function above is the classical case of a memory. You need to delete the memory before you end the routine in such a case.
    @
    void foo()
    {
    classObj *Ptr = new classObj;
    Ptr->setSomething ( 10 );
    delete Ptr;
    }
    @

    is fine. Also the next one is fine:
    @
    classObj * foo()
    {
    classObj *Ptr = new classObj;
    Ptr->setSomething ( 10 );
    return Ptr;
    }
    @
    in the latter case you hand-over the pointer with the assigned memory to the calling routine, which has to take care of the allocated memory. This becomes cumbersome depending on what you do. Therefore smart pointer have been introduced e.g. "QScopedPointer":http://qt-project.org/doc/qt-5.0/qtcore/qscopedpointer.html or "QSharedPointer":http://qt-project.org/doc/qt-5.0/qtcore/qsharedpointer.html

    You do not show enough of your code above. However, the loss of memory could be the problem you are facing there in the third code section.
    In your header workout.h in line 19, you have the classical case for use of a reference.
    @
    Workout(const QList<Interval> & lstInterval, const QString &name, const QString & createdBy, Type);
    @
    That would be better.



  • Thank you koahnig for your detailed help!

    I have changed the constructor so it use reference instead of pointers

    WORKOUT.H
    @#ifndef WORKOUT_H
    #define WORKOUT_H

    #include <QString>
    #include <QList>
    #include "interval.h"

    class Workout
    {

    public:
    enum Type
    {
    Tempo,
    Endurance
    };

    Workout();
    Workout(const QList<Interval*> &lstInterval, const QString &name, const QString &createdBy, Type);
    
    QList<Interval*> getLstInterval();
    QString getName();
    QString getCreatedBy();
    QString getTypeToString();
    Type getType();
    int getMaxPower();
    int getNbInterval();
    double getTotalLength();
    

    private :

    QList<Interval*> lstInterval;
    QString name;
    QString createdBy;
    Type type;
    int maxPower;
    double totalLength;
    

    };
    @
    WORKOUT.CPP - constructor only
    @
    Workout::Workout(const QList<Interval*> &lstInterval, const QString &name, const QString &createdBy, Type) {

    this->lstInterval = lstInterval;
    this->name = name;
    this->createdBy = createdBy;
    this->type = type;
    
    this->totalLength = 0;
    this->maxPower = 0;
    

    }
    @
    INTERVAL.H
    @#ifndef INTERVAL_H
    #define INTERVAL_H

    class Interval
    {
    public:

    Interval(const double &length, const int &targetPourcentFTP, const int &targetHR);
    
    double getLength();
    int getTargetPourcentFTP();
    int getTargetHR();
    

    private:

    double length;
    int targetPourcentFTP;
    int targetHR;
    

    };

    #endif // INTERVAL_H
    @

    Example of instanciation of both classes

    @
    void MainWindow::on_pushButton_startWorkout_clicked() {

    QList<Interval*> lstInterval;
    
    Interval *interval = new Interval(5, 100, 160);
    Interval *interval2 = new Interval(1, 80, 160);
    lstInterval.append(interval);
    lstInterval.append(interval2);
    
    Workout *work = new Workout(lstInterval, "name", "me", Workout::Endurance);
    
    workoutDialog = new WorkoutDialog(this);
    workoutDialog->initGraph(work); // ERROR HERE
    

    //ERROR MSG = Cannot convert parameter 1 form Workout* to const Workout &
    workoutDialog->setModal(true);
    workoutDialog->show();
    @

    I'm starting doing the tutorial, hopefully it will clear up stuff for me!
    The main thing that confuse me is that I don't see the difference between instantiating an object, and declaring a pointer to an object. both don't do the same thing? i.e: those 2 line of code below:

    Workout workout = new Workout(..)
    Workout *workout = new Workout(...)

    Thank you again


  • Moderators

    Nope. They don't do the same thing.
    @
    Workout workout1 (....);
    @

    There you have the object already created (instantiated). You can directly use it.

    With
    @
    Workout *workout2 = new Workout(…);
    @

    You do the same thing but the access it different (and the memory is in different places).

    if you use "*workout2" from the second version is the same as using "workout1".

    Handing them to a routine using referencing or pointer is different:
    @
    void foo1 ( const Workout & workout )
    {

    }

    void foo2 ( Workout & workout )
    {

    }

    void foo3 ( Workout * workout )
    {

    }

    void main ()
    {
    Workout workout1(...);
    Workout *workout2;
    Workout *workout3;

      workout3 = &workout1;  // you get the memory address and 
                                              // assign to pointer
      workout2 = new Workout (...);  // assigns memory
      
      foo1 ( workout1 );
      foo2 ( workout1 );
      foo3 ( &workout1 );
    
      foo1 ( *workout2 );
      foo2 ( *workout2 );
      foo3 ( workout2 );
    
      foo1 ( *workout3 );
      foo2 ( *workout3 );
      foo3 ( workout3 );
    

    }
    @

    In your code where the error is you basically do:
    @
    foo1 ( workout3 ); // compile error
    foo2 ( workout3 ); // compile error
    foo3 ( workout3 ); // this is ok, since as above
    @

    Probably best is to go through the tutorial. Afterwards you may want to make a small program and try out systematically the different ways of instantiating and referencing to functions.
    Basically you can do with such code as above, but this might overwhelm you by complexity.

    Good luck



  • [quote author="maximus" date="1380746647"]

    I'm starting doing the tutorial, hopefully it will clear up stuff for me!
    The main thing that confuse me is that I don't see the difference between instantiating an object, and declaring a pointer to an object. both don't do the same thing? i.e: those 2 line of code below:

    Workout workout = new Workout(..)
    Workout *workout = new Workout(...)

    [/quote]

    Among other reasons, "polymorphism" can only done with pointers
    http://www.cplusplus.com/doc/tutorial/polymorphism/



  • Thanks for your help guys, and the great example!
    I read the tutorial and it's much clearer now:
    http://www.cplusplus.com/doc/tutorial/
    http://www.youtube.com/playlist?list=PL2D1942A4688E9D63

    Good luck :)



  • My program run fine when I use approach #1
    But since I know the data size before execution, I want to remove the "new" so that memory is not a problem. In the final version, I want to have a fixed amount of "Workout" and some more that the user can add, for those one I will use the "new" method.

    #1
    @ QList<Interval*> lstInterval;
    Interval *interval = new Interval(.5, 80, 90, 150, Interval::WARM_UP);
    Interval *interval2 = new Interval(.5, 90, 80, 120, Interval::RECOVERY);
    Interval *interval22 = new Interval(5, 95, 70, 100, Interval::COOLDOWN);
    lstInterval.append(interval);
    lstInterval.append(interval2);
    lstInterval.append(interval22);
    Workout *work = new Workout(lstInterval, "name", "me", Workout::ENDURANCE);
    lstWork.append(work);@

    I have tried this conversion :

    #2
    @ QList<Interval*> lstInterval;
    Interval interval(.5, 80, 90, 150, Interval::WARM_UP);
    Interval interval2(.5, 90, 80, 120, Interval::RECOVERY);
    Interval interval22(8, 95, 70, 100, Interval::COOLDOWN);
    lstInterval.append(&interval);
    lstInterval.append(&interval2);
    lstInterval.append(&interval22);
    Workout work(lstInterval, "name", "me", Workout::ENDURANCE);
    lstWork.append(&work);@

    But this one fail at execution, for me both methods are supposed to do the same thing, am I missing something?

    WORKOUT.H
    @#ifndef WORKOUT_H
    #define WORKOUT_H

    #include <QString>
    #include <QList>
    #include "interval.h"

    class Workout
    {

    public:
    enum Type
    {
    TEMPO,
    ENDURANCE
    };

    Workout(QList<Interval*> lstInterval, QString name, QString createdBy, Type type);
    

    // ~Workout(); TODO

    QList<Interval*> getLstInterval();
    QString getName();
    QString getCreatedBy();
    QString getTypeToString();
    Type getType();
    int getMaxPower();
    int getNbInterval();
    double getTotalLength();
    

    private :

    QList<Interval*> lstInterval;
    QString name;
    QString createdBy;
    Type type;
    int maxPower;
    double totalLength;
    

    };

    #endif // WORKOUT_H@

    WORKOUT.CPP
    @#include "workout.h"
    #include <QDebug>

    Workout::Workout(QList<Interval*> lstInterval, QString name, QString createdBy, Type type) {

    this->lstInterval = lstInterval;
    this->name = name;
    this->createdBy = createdBy;
    this->type = type;
    
    this->totalLength = 0;
    this->maxPower = 0;
    
    
    foreach(Interval *val, this->lstInterval) {
        this->totalLength += val->getLength();
        if (val->getTargentFTP() > this->maxPower) {
            this->maxPower = val->getTargentFTP();
        }
    }
    
    
     qDebug() << "TotalLength"  << this->totalLength;
     qDebug() << "MaxPower"  << this->maxPower;
    

    }

    // -------------------------------------------
    QList<Interval*> Workout::getLstInterval() {
    return this->lstInterval;
    }

    QString Workout::getName() {
    return this->name;
    }
    QString Workout::getCreatedBy() {
    return this->createdBy;
    }
    Workout::Type Workout::getType() {
    return this->type;
    }
    QString Workout::getTypeToString() {
    if (this->type == Workout::ENDURANCE ) {
    return "Endurance";
    }
    else if (this->type == Workout::TEMPO) {
    return "Tempo";
    }
    else
    return "Workout type not defined";
    }
    int Workout::getMaxPower() {
    return this->maxPower;
    }
    int Workout::getNbInterval() {
    return this->lstInterval.size();
    }
    double Workout::getTotalLength() {
    return this->totalLength;
    }@



  • bq. But since I know the data size before execution, I want to remove the “new” so that memory is not a problem

    you use dynamic objects not just for memory management but also for speed by avoiding a lot of wasting copy operations ... if you really switched to C++ then let the pointers be your friends :) , you cannot really benefit of C++ without pointers

    to your issue in method #2 I think you go out of scope with your lists and the addresses will point out to some "destroyed" objects, as with "new" they persist in dynamic memory(heap) till they are destroyed ... and by the way I cannot see any code deleting the dynamic objects and freeing memory though, perhaps you have some memory leak

    some more hints: put your seters and geters in class declaration and they will be inlined and the code execution will be little faster, and I use to return a reference to list members in a class to avoid again a copy
    operation, as Workout::getLstInterval()

    Cheers!



  • Thanks for your hints mister!
    Not so easy to switch from Java to C++ so all tips are appreciated :)

    I was afraid of using dynamic memory because I read it can generate exceptions and you need to check every time you do a "new" that it has been successful.

    As for deleting the dynamic objects, this QList of <Workout*> will never be deleted, it is part of my MainWindow(Main program) and it needs to be there at all time, it will eventually grow or shrink depending on user operation.

    When you say to put seters and geters in class declaration, you mean implement them directly in the header file ?

    to return a reference to list member, I need to return a pointer right?
    so instead of
    QList<Interval*> getLstInterval();
    I would use
    QList<Interval*> * getLstInterval();
    To return a QList pointer that point to many pointers of type Interval?
    Can get confusing fast hehe

    Merci!



  • maximus, handle the resources(memory, file, mutex) in c++ is pretty simple with the helps of standard library, exactly, I found it is much more easier to handle resource than any language come with garbage collector I know.I strongly suggest you study what is "RAII", this could make your life much more easier when developing with c++.

    Also, QList<T> will handle the resource of T automatically atleast it is storing a raw pointer which hold onto some resources.

    Example 1 : handle resource by containers
    @std::vector<Interval> A;@
    This one will be destroy when it is out of scope
    If you don't need pointer, I would recommend the style of example 1

    Example 2 : handle resource by raw pointer and container
    @
    std::vector<*Interval> A;
    A.emplace_back(new interval);
    A.emplace_back(new interval);
    ......
    for(auto data : A)
    {
    delete data;
    }
    @
    you need to destroy the resource in exmaple 2

    example 3: handle resource by container and smart pointer
    @
    std::vector<std::uqniue_ptr<Interval>> A;
    A.emplace_back(new Interval);
    @
    don't need to release the resource by yourself in example 3
    if you need the power of pointer I would recommend solution 3

    There are different smart pointer in the standard, pick one which suit
    your job most.

    In modern c++(not c with classes), resource management is a piece of cake(atleast
    you are building some infrastructure like containers, smart pointer and so on),make
    sure you are learning the c++ with modern way which proposed by Bjarne(father of c++),
    herb, Lippman and other c++ gurus, stay away from "c with classes", this will help you develop
    better quality, higher performance codes.

    If you want to have a solid foundation about c++, please give c++ primer 5 edition(not previous version)
    a look, you only need to study chapter 1~16, it is enough for application developers.

    ps : RAII is same as "let the class to handle the resources "



  • [quote author="maximus" date="1381270434"]Thanks for your hints mister!
    Not so easy to switch from Java to C++ so all tips are appreciated :)

    I was afraid of using dynamic memory because I read it can generate exceptions and you need to check every time you do a "new" that it has been successful.

    As for deleting the dynamic objects, this QList of <Workout*> will never be deleted, it is part of my MainWindow(Main program) and it needs to be there at all time, it will eventually grow or shrink depending on user operation.

    When you say to put seters and geters in class declaration, you mean implement them directly in the header file ?
    to return a reference to list member, I need to return a pointer right?
    so instead of
    QList<Interval*> getLstInterval();
    I would use
    QList<Interval*> * getLstInterval();
    To return a QList pointer that point to many pointers of type Interval?
    Can get confusing fast hehe

    Merci![/quote]

    There are another way to get the reference of the object in c++
    @
    QList<Interval>& getLstInterval();
    @

    What are the difference between & and pointer(*)?

    1 : the way to access the variable

    @
    int max(int *a, int *b)
    {
    return *a > *b ? *a : *b;
    }
    @

    @
    int max(int &a, int &b)
    {
    return a > b ? a : b;
    }
    @

    you access the reference like normal variable

    2 : you can't change the address of the reference

    @
    int a = 3;
    int *ptr = &a;
    std::cout<<*ptr<<std::endl; //output 3
    int b = 4;
    ptr = &b; //now ptr point to the address of b
    std::cout<<*ptr<<std::endl; //output 4
    @

    @
    int a = 3;
    int &ref = a;
    std::cout<<ref<<std::endl; //3
    int b = 4;
    ref = 4; //compile time error, you can't point to another address by reference
    @

    please study c++ primer 5 edition from ch1~ch16
    This could save you a lot of times--to debug and maintain your codes



  • Thank you.

    If I understand correctly , some smart pointers act like Java object, when no reference exist to an object, the object is deleted.

    I just question why this isn't the normal behavior of C++ pointer, because typing every time really add some unnecessary syntax to the code. /Java rant over :)

    So I will flush all my "standard" pointer and use the one in the list below depending on the use case.

    Local variables: auto_ptr
    Class members: Copied pointer std::unique_pt
    STL Containers Garbage collected pointer (e.g. reference counting/linking) shared_ptr
    Explicit ownership transfer Owned pointer



  • [quote author="maximus" date="1381325557"]
    I just question why this isn't the normal behavior of C++ pointer, because typing every time really add some unnecessary syntax to the code. /Java rant over :)
    [/quote]

    As far as I know, there are two reasons

    1 : for efficiency, runtime performance of c++ can't lower than c, this is one of the most important foundation of c++.Whatever, c++ is a high level system programming language, it most provide us full choices between abstraction and performance(the power close to the metal).

    2 : destructor make c++ more difficult to support implicit garbage collector.

    3 : (just my guess), it is not that important for many c++ programmers.

    Although the standard committee do want to support garbage collector, but I don't know when this will come true.Even it do support, I think I wouldn't need it in most of the times, RAII is easy enough for me, and I like to know the life times of the resources.

    The beauty of RAII compare to garbage collector is RAII can handle "any" resource(ex : mutex, file system and so on).

    [quote author="maximus" date="1381325557"]
    Local variables: auto_ptr
    Class members: Copied pointer std::unique_pt
    STL Containers Garbage collected pointer (e.g. reference counting/linking) shared_ptr
    Explicit ownership transfer Owned pointer[/quote]

    1 : Please forget about auto_ptr, it is deprecated, just use unique_ptr if you need to hold a single entity of the object.

    2 : unique_ptr can't copied, it can move only

    @
    std::unique_ptr<T> A(new T);
    std::unique_ptr<T> B = A //oh, compile time error
    @

    @
    std::unique_ptr<T> A(new T);
    std::unique_ptr<T> B = std::move(A); //ok, we move it
    @

    move means "B steal the resource hold by A"
    the logic under the is like this
    @
    int num = 10;
    int *A = #
    int *B = A;
    A = nullptr;
    @
    B point to the address of A, so B have the resource of A held.
    no memory allocation, this is fast and safe(wouldn't throw any exception)

    3 : shared_ptr, as the name show, it can shared the same object(reference count technique), do remember the trap of "circular dependency" when using shared_ptr.
    "How to avoid memory leak by shared_ptr":http://stackoverflow.com/questions/1826902/how-to-avoid-memory-leak-with-boostshared-ptr

    4 : If you don't need the power of pointer(like runtime polymorphism), you could just use containers to store your resource.

    @
    std::vector<T> A(10); //allocate 10 T(some sort of object)
    @

    5 : why so many smart pointer?
    Because there are no silver bullet to guard the resource(especially when you want to provide the users best performance).

    There are too many different between java and c++, please spend some times to study c++ primer 5(ch1~ch16) if you want to develop high quality, good performance applications by c++.

    c++ is a multi-paradigm language, it support OOP, procedural, stl, generic programming(not the same as the java generic, in c++, java generic is almost equal to dynamic polymorphism), template meta programming(ignore it if you don't need to develop something like boost),functional programming.

    "learning c++ properly(not c with classes)":http://programmers.stackexchange.com/questions/48401/learning-c-properly-not-c-with-classes


Log in to reply
 

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