0

I have the following class called DATA.

enum DATATYPE{DATATYPE_CONSTANT=0, DATATYPE_NUMBER,DATATYPE_STRING, DATATYPE_MATRIX, DATATYPE_OBJECT};

struct DATA //A Data container for the variable
{
    DATA(DATATYPE type,int row=0,int col=0)
    {
        m_str=0;m_number=0;

        m_DataType=type;

        if(type==DATATYPE_NUMBER) m_number=new double;
        if(type==DATATYPE_STRING) m_str=new string("");

        cout<<"In constructor"<<endl;
        //if(type==DATATYPE_MATRIX) m_matrix= new MatrixXd(row,col);
    }
    ~DATA()
    {
        if(m_str) m_str->clear();
        if(m_number) {delete m_number; m_number=0;}

        std::cout<<"In Destructor"<<std::endl;
        //if(m_matrix) {delete m_matrix; m_matrix=0;}
    }

    DATA(const DATA& other)
    {
        m_number=other.m_number;
        m_str=other.m_str;
        m_DataType=other.m_DataType;

        cout<<"In copy constructor"<<endl;
    }

    DATA& operator=(const DATA& other)
    {
        m_number=other.m_number;
        m_str=other.m_str;
        m_DataType=other.m_DataType;

        cout<<"In operator="<<endl;

        return *this;
    }

    DATATYPE GetType()
    {
        return m_DataType;
    }

    double* GetNumber()
    {
        return m_number;
    }

    void SetNumber(const double& val){*m_number=val;}


    string* GetString()
    {
        return m_str;
    }

private:
    DATATYPE m_DataType;
    string* m_str;
     //MatrixXd* m_matrix;
    double* m_number;
};

And I have the following test:

DATA GetData();

int main()
{
    cout<<"Before GetData call"<<endl;
    DATA dat=GetData();
    //DATA dat2=dat;
    cout<<*(dat.GetNumber())<<endl;
    cout<<"After Get Data call"<<endl;

    cout << "Exiting main" << endl;
    return 0;
}


DATA GetData()
{
    cout<<"In Get Data"<<endl;
    DATA ret(DATATYPE_NUMBER);
    double d=5;
    ret.SetNumber(d);
    cout<<"Exiting GetData"<<endl;
    return ret;
}

After running the test the output is:

Before GetData call

In Get Data

In constructor

Exiting GetData

5

After Get Data call

Exiting main

In Destructor

I have the following questions:

  1. When I call DATA dat=GetData(); it neither calls constructor, copy constructor nor equal operator. How is dat object constructed. Also what exactly compiler do when returning from GetData?

  2. For the DATA structure, or in general aggregate data types, is it always a good idea to initialize member variables with new? What happens to member variables when I initialize say DATA *d=new DATA(DATATYPE_NUMBER) ? Is it getting more error prone for memory leaks?

aruisdante
  • 8,875
  • 2
  • 30
  • 37
macroland
  • 973
  • 9
  • 27
  • 2
    [#1](http://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization). #2: No, use regular variables, not pointers. Any copies you make of your class are causing memory leaks right now (actually, any objects you make in the first place cause leaks, too). – chris Jul 25 '14 at 02:59
  • 3. Try switching to C++11 mode and use scoped enumerations. Also, less caps. – Potatoswatter Jul 25 '14 at 03:01
  • Also learn to use namespaces. `using namespace std;` is a deal breaker. – Shoe Jul 25 '14 at 03:12
  • The whole code is actually in a namespace; however, for convenience I preferred to "extract" it from the namespace. – macroland Jul 25 '14 at 04:27

3 Answers3

1

Question 1

When I call DATA dat=GetData(); it neither calls constructor, copy constructor nor equal operator. How is dat object constructed. Also what exactly compiler do when returning from GetData?

Answer That is a result of something called return value optimization (RVO). You can read more about them here.

In g++, you can disable RVO by using the flag -fno-elide-constructors. If you do that with your code, you will see messages from the copy constructor.

Question 2

For the DATA structure, or in general aggregate data types, is it always a good idea to initialize member variables with new? What happens to member variables when I initialize say DATA *d=new DATA(DATATYPE_NUMBER) ? Is it getting more error prone for memory leaks?

Answer

There are three question in that.

Answer 2.1

The answer to that question is "depends on your application". For some, having objects as member data makes sense while for others, having pointers to objects make sense. When you use pointers to objects, you have to follow the Rule of Three, which has become the Rule of Five in C++11.

Answer 2.2

The member variables are initialized just as they would be had you used:

Data d = DATA(DATATYPE_NUMBER);

Answer 2.3

Using dynamic memory has benefits, but it also has its down sides. Any time you use dynamic memory allocation, you are entering into more error prone code. You have to worry about its potential bad side effects:

  1. Dangling pointers.
  2. Lost pointers.
  3. Accessing memory beyond what was allocated.
Community
  • 1
  • 1
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Thanks! I will try [-fno-elide-constructors]. I have not heard the "copy-elision", suggested by David Rodriguez, before and will take a look at that as well. – macroland Jul 25 '14 at 04:25
1

When I call DATA dat=GetData(); it neither calls constructor, copy constructor nor equal operator. How is dat object constructed. Also what exactly compiler do when returning from GetData?

equal operator: Even if it may look like you are assigning, that is not the case. The syntax T t = u; does not contain any assignment, but rather copy construction.

constructor: The constructor is called inside the function, then a copy is returned and that copy is in turn copied over the destination object. Except it isn't. The language allows for copy-elision which means that the compiler is allowed to remove the copies by placing the three objects (ret inside the function, returned object and dat inside main) in the same memory location.

I am not sure how much the exact details matter or will help, but in most ABIs (in all I know) a function returning by value an object is transformed by the compiler into a function that takes a pointer to the location where the object will live.

T f(int x) {
   T tmp(x);
   return tmp;
}
int main() {
   T t = f(1);
}

Is transformed into:

void f(void *__ret, int x) {
   new (__ret) T(x); // constructor call
   return;                               // return does not *copy*
}
int main() {
   [[uninitialized]] T t;                // space is reserved, no construction
   f(&t, 1);
}

That is literally how copy-elision functions, f created the tmp object on top of the returned value (the argument) and the caller in main placed the t and the return value of f over the same memory location.

For the DATA structure, or in general aggregate data types, is it always a good idea to initialize member variables with new? What happens to member variables when I initialize say DATA *d=new DATA(DATATYPE_NUMBER) ? Is it getting more error prone for memory leaks?

You tell me. But before you answer, note that your DATA type has a memory leak.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • I can see that the memory leak is from not deleting the string m_str. Is there a good resource you can suggest to read on copy-elision? – macroland Jul 25 '14 at 04:20
  • @macroland: I wrote two introductory articles in the past, [here](http://definedbehavior.blogspot.com/2011/08/value-semantics-nrvo.html) and [here](http://definedbehavior.blogspot.com/2011/08/value-semantics-copy-elision.html). They are high level and don't go into the details of the different ABIs and calling conventions. From a high level view, the language, you just need to know that it *happens*, how compilers implement it is really an implementation detail, although the transformation in this answer is the guts of it. – David Rodríguez - dribeas Jul 25 '14 at 04:33
  • Rodriguez: Thanks for the links. I have compiled the above code with CodeBlocks and presumably it is using `-fno-elide-constructors` by default. The same code for `DATA` behaved very differently when compiled with Eclipse CDT and I could have tracked the steps, the use of copy constructor, when returning from a function which completely changed the behaviour of the program. I started to get values like E-208 for `m_number`. It was very trickly to catch the error, but rather than `new double` I am just initializing with `m_number=0`. – macroland Jul 27 '14 at 10:30
0

Make your constructors explicit and remove the default parameters on DATA(enum,int,int). When GetData() constructs the object with a single parameter you are most likely getting a compiler generated default constructor.

You can save memory by putting m_str & m_number in a union rather than separate member variables:

DATA {
...

union U {
   string* m_string;
   double* m_number;
}
    DATATYPE m_DataType;
    U m_data;
}

Using pointers for members is both fine and common when the members are large or can be constructed late for performance. But you do have to take additional care to avoid memory leaks. OTOH, if the members are small, the convenience of a regular member variable is frequently best.

Brad
  • 3,190
  • 1
  • 22
  • 36