Initialising Array of objects calls constructor twice

The constructor for Node has a default value for the parameter it's receiving. When new[] is used, this default constructor is called. In the loop the constructor with the right value is called. How can I prevent this double calling of the constructor? I am creating multi-dimensional arrays with millions of nodes and this seams very inefficient. All I need new[] to do is create the pointers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, const char * argv[]) {
    std::cout<<"Start main"<<std::endl;
    
    Node* tmp= new Node[3];
    std::cout<<"Checkpoint main"<<std::endl;
    for(int i=0;i<3;i++){
        tmp[i]=*new Node(4);
    }
    delete[] tmp;
    tmp=NULL;
    
    std::cout<<"End main"<<std::endl;
    return 0;
}


Start main
Created Node no_name
Created Node no_name
Created Node no_name
Checkpoint main
Created Node no_name
Created Node no_name
Created Node no_name
Deleted Node no_name
Deleted Node no_name
Deleted Node no_name
End main



Thank you in advance
Last edited on
1. You could assign 4 to tmp[i], you'd need to call a method on Node to do that.
1
2
3
4
Node* tmp= new Node[3];
for (size_t i = 0; i != 3; ++i)
    tmp[i]->set_value(4);
delete [] tmp;


2. You could use an array of pointer to Node instead. That would allow you to easily add new nodes to an initally empty list.
1
2
3
4
typedef Node* PNode;
Node** tmp= new PNode[3];
for (size_t i = 0; i != 3; ++i)
    tmp[i] = new Node(4);

Then you'd need to release the nodes yourself.
1
2
3
for (size_t i = 0; i != 3; ++i)
    delete tmp[i];
delete [] tmp;


3. You could use std::vector.
1
2
3
4
std::vector<Node> tmp;
tmp.reserve(3);
for (size_t i = 0; i != 3; ++i)
    tmp.emplace_back(4);
Last edited on
@kbw

1. Yes, this is what I will do. I was just wondering wether it was possible to prevent new[] from calling the default constructor. Or (ideally) use new[] for an object that has no default constructor.

2. I'm not sure what you mean. Am I not using an array of pointers now? How can I add new nodes to the end?

3. I've never really gotten into using std::vector out of fear of the overhead. Do you know how much slower it is?

Thanks
3. I've never really gotten into using std::vector out of fear of the overhead. Do you know how much slower it is?


I think that's a misconception you have there. Did you know that STL containers internally use new to store their data on the heap? If it is ever so slightly slower (I doubt that it will, I have been surprised a number of times how quick it is), balance against that gains from not having to worry about memory management. The biggest problem with new, is that if something throws an exception, delete is never reached - leaving a memory leak. It is a known rule not to use new unless one is writing a library.

Some other basic advantages of STL containers:

They have their own iterators, so one can use ranged based for loop;

They have their own operators;

STL algorithms can be applied to them;

They can easily store any custom type. So if it wasn't a Node: say a Page, or a Person; then it's easy to make a container of them. Whereas you have to write code.

Your code will be cleaner, shorter and easier to understand if you use a STL container such as std::vector.
Last edited on
I think you could use malloc to just get memory and not call constructor, then use replacement new to make Node objects on the memory, like this:
{
std::cout<<"Start main"<<std::endl;

Node* tmp= (Node *)malloc(sizeof(Node) * 3);
std::cout<<"Checkpoint main"<<std::endl;
for(int i=0;i<3;i++){
new (&tmp[i])Node(4);
}
for (int i = 0; i < 3; i++) {
tmp[i].~Node();
}
free(tmp);
tmp=NULL;

std::cout<<"End main"<<std::endl;
return 0;
}

In this way you should manually call destructor and then free memory.
But I think it is better to use STL, and it does not suffer much efficiency.
2. I'm not sure what you mean. Am I not using an array of pointers now? How can I add new nodes to the end?


I copy your code here to make it easier to reference.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, const char * argv[]) {
    std::cout<<"Start main"<<std::endl;
    
    Node* tmp= new Node[3];
    std::cout<<"Checkpoint main"<<std::endl;
    for(int i=0;i<3;i++){
        tmp[i]=*new Node(4);
    }
    delete[] tmp;
    tmp=NULL;
    
    std::cout<<"End main"<<std::endl;
    return 0;
}


The short answer is "no". You are not using an array of pointers.

In line 4 you dynamically allocate an array of 3 Node objects that are contiguous on the heap. The value of tmp is a single pointer to a Node that happens to point to the first Node in the array. Subsequent Nodes in the array can be accessed using pointer arithmetic (tmp + 1), or array indexing (tmp[1]). Pointer arithmetic yields a new pointer pointing to another Node in the array. Array indexing yields an actual Node in the array--i.e. the pointer has already been dereferenced. Both of the above assume that the bounds of the array have not been violated

So, the following statements would be true:

1
2
tmp + 2 == &tmp[2]
*(tmp + 1) == tmp[1]


So, you have an array of Node objects with a single pointer--not an array of pointers to Nodes.

In line 7 you are dynamically creating another Node object and then copying its contents into the array. The newly created Node object is subsequently ignored, and you have a memory leak.

Compare this with option #2 provided by @kbw.

1
2
3
4
typedef Node* PNode;
Node** tmp= new PNode[3];
for (size_t i = 0; i != 3; ++i)
    tmp[i] = new Node(4);


In this code block, line 2 actually dynamically allocates an array of pointers-to-Node. So, there are 3 pointers (to Node) allocated contiguously on the heap. The value tmp now contains the address of the first pointer in the Array. Thus, the type of tmp is a Node** (a pointer to a pointer to a Node). Line 4 subsequently allocates a single Node object (dynamically) and places it address into the array. It is also possible, if you wish, to allocate an array of Node objects and place the address of the first element in each array into the tmp element. This would give you a jagged 2-dimensional array.

The upshot is, use vectors. There is no noticeable overhead, it's safer, and you can create the elements as you need them without copying (using emplace_back). And if you need multiple-dimension arrays, you can simply write the following:

1
2
std::vector<std::vector<Node> > twoDimensionalArray;
std::vector<std::vector<std::vector<Node> > > threeDimensionalArray;


By the way, I wouldn't mess around with the replacement new suggested above. It can be done, but going back to C constructs to play with memory allocation and then using a rarely-used version of new seems to be the wrong solution in my mind.



Last edited on
Topic archived. No new replies allowed.