Denomycor
Denomycor

Reputation: 157

New array allocations

So i have this class called point, just to log to the console everytime an object gets constructed and destroyed. And i did the following:

#include <iostream>

struct point{
    point() {
        std::cout << "ctor\n";
    }
    ~point() {
        std::cout << "dtor\n";
    }
};
    int main(){
    int x = 3;
    point* ptr = new point[x]{point()};
    delete[] ptr;
}
ctor
dtor
dtor
dtor

This ended up calling the constructor just once, and the destructor 3 times, why? I know that this is bad, probably ub, but i would like to understand why. This other allocations give me the "expected" output:

int x = 3;
constexpr int y = 3;
point* ptr1 = new point[3];
point* ptr2 = new point[x];
point* ptr3 = new point[y]{point()};
ctor
ctor
ctor
dtor
dtor
dtor

Im using visual studio 19 latest version.

Upvotes: 6

Views: 165

Answers (3)

kishoredbn
kishoredbn

Reputation: 2087

This might not give you any direct answer, but just presenting some observations.

As @interjay has commented, we could see that there is a difference between how the same piece of code runs in VC++ and other popular compilers. I didn't see any deviation from standards were mentioned in MSDN, and infact VC++ do support for list and other iniliazilers following the standards in VS2017.

Now while playing with different notations of rewriting the same thing, what I observed was something interesting.

Verifying what is documented in MSDN I see that for the following notation,

point* ptr = new point[x]{};

Output is as expected, and functionally it is consistent across different compilers.

ctor
ctor
ctor
dtor
dtor
dtor

Also with the following stack based array allocation,

 point ptr[3] = {point()};

gets us

ctor
ctor
ctor
dtor
dtor
dtor

So far everything looks good and the results matches with other compilers, not until we call the following code:

point* ptr = new point[x]{point()};

Just coming with best of what I understand from this is that this could be a implementation glitch in VC++.

As you call new operator along with constructor initialization inside the braces, may be for VC++ this is not a recommended usage, and internally it falls back to some custom initialization handler where it is expected to call constructors when explicitly assigned for each indices, while rest everything else is just allocated with uninitialized memory (no constructor gets called). This is a clear undefined behavior (UB).

While, in the couple of examples that I have shown earlier, it seems like (looking at the call stack in Windbg, I could be entirely wrong with this) internally VC++ at the runtime calls something similar to vector constructor initializer which calls the default constructor (only) iteratively for all indices.

Again this can be verifies using following piece of code, where calling a constructor overload

 point(int i) {
            std::cout << "ctor "<<i<<"\n";
        }

from

point ptr[3] = {point(10)}; //not a default constructor

spits out:

ctor 10
ctor
ctor
dtor
dtor
dtor

This is weird and an abnormal behavior, which is nothing new in VC++. What I can suggest is that try using the following notation to get consistant result in cross different compilers:

point* ptr = new point[x]{};

Upvotes: 0

Vin&#237;cius
Vin&#237;cius

Reputation: 15718

This is a compiler bug.

By using operator new without a constant defined type size MSVC compiler will call the class object constructor and destructor as many times as explicitly specified at initializer list and/or array size.

#include <iostream>

struct point {
    point() {
        std::cout << "ctor\n";
    }
    ~point() {
        std::cout << "dtor\n";
    }
};
int main() {
    int x = 3;
    point* ptr = new point[x]{point()};
    delete[] ptr;
}

As stated will call as explicitly specified point ctor once.

This can be asserted by: point* ptr = new point[x]{point(), point()};

  • MSVC Output: ctor ctor dtor dtor dtor.
  • GCC: ctor ctor ctor dtor dtor dtor (which should be guaranteed)

And even a throwable array out of bound exception UB: point* ptr = new point[x]{point(), point(), point(), point(), point() }; follows the behavior.

  • MSVC Output: ctor ctor ctor ctor ctor dtor dtor dtor.
  • GCC: terminate called after throwing an instance of 'std::bad_array_new_length'

Too many initializers is correctly detected if the defined size is constant. i.e const int x = 3 or constexpr int x = 3

Upvotes: 3

Remy Lebeau
Remy Lebeau

Reputation: 595857

This ended up calling the constructor just once, and the destructor 3 times, why? 

You need to log the copy and move constructors as well:

#include <iostream>

struct point{
    point() {
        std::cout << "default ctor\n";
    }
    point(const point &) {
        std::cout << "copy ctor\n";
    }
    point(point &&) {
        std::cout << "move ctor\n";
    }
    ~point() {
        std::cout << "dtor\n";
    }
};

Upvotes: -1

Related Questions