-1

Consider we create the array using this way:

T* arr = new T[num];

And now because of some reasons we understood that we need simply delete that array but without calling any T destructors.

We all know that if we write next:

delete arr;

the T destructor will be called. If we write this:

delete[] arr;

the num destructors would be called. Having played with pointers, you realize that new inserts before the result pointer the unsigned long long value that represents the number of allocated T instances. So we try to outwit the C++ trying to change that value to number of bytes that arr occupies and delete it as (char*) in hope that in this case the delete would not call the destructors for T instances and simply free occupied memory. So you write something like this:

typedef unsigned long long;
unsll & num = *(unsll)((char*)arr-sizeof(unsll));
num = num*sizeof(T);
delete ((char*)arr);

But that doesn't work and C++ creates the trigger breakpoint(run time error) when trying to delete this. So that doesn't work. And a lot of other playing with pointers doesn't work as at least some error(compile- or run-time) occurs. So the question is:

Is that possible to delete an array of classes in C++ without calling their destructors?

  • 2
    `delete arr` is undefined behaviour; it might do anything. – Alan Stokes Apr 14 '16 at 21:45
  • 3
    Why do you want to not call destructors? This is normally a really bad idea. – Alan Stokes Apr 14 '16 at 21:45
  • *So we try to outwit the C++* -- Why do you want to "outwit C++"? -- *you realize that new inserts before the result pointer the unsigned long long value that represents the number of allocated T instances* -- That's news to me. – PaulMcKenzie Apr 14 '16 at 21:48
  • It sounds like you have behaviour in your destructor that should not be there. The best approach is to move that code out. However, if you implement your own allocator, you could probably set it up so you can free the memory behind the compiler's back. But that sounds like a bad idea. – Zastai Apr 14 '16 at 21:50
  • @AlanStokes, because I need to replace the array. I copy the block of memory to other place and can normally continue working with it. But the reason of such manipulations is freeing some memory, so I need to free previously used memory without calling destructors, as if T is 'struct' with pointers or, for example, 'HANDLE''s it would likely make these data unusable. – Volodymyr Sendetskyi Apr 14 '16 at 21:51
  • 1
    @VolodymyrSendetskyi What you're looking for is called move constructors. http://en.cppreference.com/w/cpp/language/move_constructor – Alan Stokes Apr 14 '16 at 21:52
  • 1
    Read about the [Rule of Three](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) and the [Rule of Five](https://stackoverflow.com/questions/4782757/rule-of-three-becomes-rule-of-five-with-c11) and then use normal C++ semantics. Even if you find some hack to get the behavior you ask about, it would be horrible code. – Baum mit Augen Apr 14 '16 at 21:59
  • 1
    @AlanStokes, thank you for pointing that out. That do really solves my problem. But that doesn't answer the question. – Volodymyr Sendetskyi Apr 14 '16 at 22:00
  • How are you "copy the block of memory to other place" ? – M.M Apr 14 '16 at 22:03
  • @M.M, for example: `memcpy(newArr, arr, sizeof(T)*num);` - that really works. – Volodymyr Sendetskyi Apr 14 '16 at 22:06
  • 1
    @VolodymyrSendetskyi You asked the wrong question :-) – Alan Stokes Apr 14 '16 at 22:06
  • @VolodymyrSendetskyi That `memcpy` is also undefined behaviour. Appears to work is not the same as guaranteed to work. – Alan Stokes Apr 14 '16 at 22:07
  • 1
    What if a `T` contains a pointer to a part of itself? – M.M Apr 14 '16 at 22:08

3 Answers3

2

Perhaps you want ::operator delete[](arr).

(See http://en.cppreference.com/w/cpp/memory/new/operator_delete)

But this still has undefined behaviour, and is a terrible idea.

Alan Stokes
  • 18,320
  • 3
  • 41
  • 63
  • Where is the undefined behaviour? – M.M Apr 14 '16 at 22:02
  • Ending the lifetime (by freeing the memory) without calling the destructor is UB I believe. (Chapter and verse not to hand.) – Alan Stokes Apr 14 '16 at 22:04
  • OK, I guess you refer to [basic.life]/4 "[...] any program that depends on the side effects produced by the destructor has undefined behavior." So it would depend on what OP's destructors contained. – M.M Apr 14 '16 at 22:08
  • @M.M That paragraph is under debate right now. I think there are other relevant rules (see http://en.cppreference.com/w/cpp/language/lifetime). I'm assuming arbitrary `T`, since no constraints are specified. – Alan Stokes Apr 14 '16 at 22:11
  • Are you referring to any particular part of that page? It seems to say the same thing, "[...] if the program does not rely on the side effects of the destructor." – M.M Apr 14 '16 at 22:18
  • I wonder if a `new[]` expression is allowed to request more storage than needed and return a pointer `p` to somewhere inside the allocated block, and then a `delete[]` expression doing the opposite? In such a case it would be pretty undefined to just try to deallocate via `p`. – Cheers and hth. - Alf Apr 14 '16 at 22:22
  • @M.M Umm, no; I misread it. I think your citation is the right one - although I'm not convinced the words used mean what they're supposed to mean. – Alan Stokes Apr 14 '16 at 22:27
  • @Cheersandhth.-Alf Yes. That's why I suggested `operator delete []` rather than `operator delete`. – Alan Stokes Apr 14 '16 at 22:28
  • Alf refers to [this issue](http://stackoverflow.com/questions/8720425/array-placement-new-requires-unspecified-overhead-in-the-buffer). That thread actually doesn't discuss the consequences for `operator delete[]` but AFAICS it has the same issue (you need to perform the unspecified adjustment, you can't just pass the result of the `new`-expression to `operator delete[]`). So really OP will need to use separate allocation and construction. – M.M Apr 14 '16 at 22:32
  • Also not mentioned yet is that if `T` overloads `operator new[]` then you'd want to call `T::operator delete[]`, not `::operator delete[]` – M.M Apr 14 '16 at 22:33
  • Ah, interesting. Agreed, a sufficiently evil `new[]` would break this. And also agree about the class overloading the operators; that at least was an intentional omission in the interests of simplicity. – Alan Stokes Apr 14 '16 at 22:37
  • This triggers me a breakpoint if I don't use primitive types. And I don't. – Volodymyr Sendetskyi Apr 15 '16 at 14:35
1

One simple way to deallocate without calling destructors is to separate allocation and initialization. When you take proper care of alignment you can use placement new (or the functionality of a standard allocator object) to create the object instances inside the allocated block. Then at the end you can just deallocate the block, using the appropriate deallocation function.

I can't think of any situation where this would be a smart thing to do: it smells strongly of premature optimization and X/Y-problem (dealing with problem X by imagining impractical Y as a solution, then asking only about Y).

A new-expression is designed to couple allocation with initialization, so that they're executed as an all-or-nothing operation. This coupling, and ditto coupling for cleanup and deallocation, is key to correctness, and it also simplifies things a lot (i.e., inside there's complexity that one doesn't have to deal with). Uncoupling needs to have a very good reason. Avoiding destructor calls, for e.g. purposes of optimization, is not a good reason.

Cheers and hth. - Alf
  • 135,616
  • 15
  • 192
  • 304
0

I'm only going to address your specific question of

Is that possible to delete an array of classes in C++ without calling their destructors?

The short answer is yes.

The long answer is yes, but there's caveats and considering specifically what a destructor is for (i.e. resource clean up), it's generally a bad idea to avoid calling a class destructor.

Before I continue the answer, it should be noted that this is specifically to answer your question and if you're using C++ (vs. straight C), using this code will work (since it's compliant), but if you're needing to produce code in this way, you might need to rethink some of your design since code like this can lead to bugs/errors and general undefined behavior if not used properly.

TL;DR if you need to avoid destructors, you need to rethink your design (i.e. use copy/move semantics or an STL container instead).

You can use malloc and free to avoid constructor and destructor calls, example code:

#include <iostream>
#include <cstdio>

class MyClass {
    public:
        MyClass() : m_val(0)
        {
            this->init(42);
            std::cout << "ctor" << std::endl;
        }

        ~MyClass()
        {
            std::cout << "dtor" << std::endl;
        }

        friend std::ostream& operator<<(std::ostream& stream, const MyClass& val)
        {
            stream << val.m_val;
            return stream;
        }

        void init(int val)
        {
            /* just showing that the this pointer is valid and can
            reference private members regardless of new or malloc */
            this->_init(val);
        }

    private:
        int m_val;

        void _init(int val)
        {
            this->m_val = val;
        }   
};

template < typename Iterator >
void print(Iterator begin, Iterator end)
{
    while (begin != end) {
        std::cout << *begin << std::endl;
        ++begin;
    }
}

void set(MyClass* arr, std::size_t count)
{
    for (; count > 0; --count) {
        arr[count-1].init(count);
    }
}

int main(int argc, char* argv[])
{
    std::cout << "Calling new[10], 10 ctors called" << std::endl;
    MyClass* arr = new MyClass[10]; // 10 ctors called;
    std::cout << "0: " << *arr << std::endl;
    set(arr, 10);
    print(arr, arr+10);
    std::cout << "0: " << *arr << std::endl;
    std::cout << "Calling delete[], 10 dtors called" << std::endl;
    delete[] arr; // 10 dtors called;

    std::cout << "Calling malloc(sizeof*10), 0 ctors called" << std::endl;
    arr = static_cast<MyClass*>(std::malloc(sizeof(MyClass)*10)); // no ctors
    std::cout << "0: " << *arr << std::endl;
    set(arr, 10);
    print(arr, arr+10);
    std::cout << "0: " << *arr << std::endl;
    std::cout << "Calling free(), 0 dtors called" << std::endl;
    free(arr); // no dtors

    return 0;
}

It should be noted that mixing new with free and/or malloc with delete results in undefined behavoir, so calling MyClass* arr = new MyClass[10]; and then call free(arr); might not work as "expected" (hence the UB).

Another issue that will arise from not calling a constructor/destructor in C++ is with inheritance. The above code will work with malloc and free for basic classes, but if you start to throw in more complex types, or inherit from other classes, the constructors/destructors of the inherited classes will not get called and things get ugly real quick, example:

#include <iostream>
#include <cstdio>

class Base {
    public:
        Base() : m_val(42)
        {
            std::cout << "Base()" << std::endl;
        }

        virtual ~Base()
        {
            std::cout << "~Base" << std::endl;
        }

        friend std::ostream& operator<<(std::ostream& stream, const Base& val)
        {
            stream << val.m_val;
            return stream;
        }

    protected:
        Base(int val) : m_val(val)
        {
            std::cout << "Base(" << val << ")" << std::endl;
        }

        void _init(int val)
        {
            this->m_val = val;
        }

        int m_val;
};

class Child : public virtual Base {
    public:
        Child() : Base(42)
        {
            std::cout << "Child()" << std::endl;
        }

        ~Child()
        {
            std::cout << "~Child" << std::endl;
        }

        void init(int val)
        {
            this->_init(val);
        }
};

template < typename Iterator >
void print(Iterator begin, Iterator end)
{
    while (begin != end) {
        std::cout << *begin << std::endl;
        ++begin;
    }
}

void set(Child* arr, std::size_t count)
{
    for (; count > 0; --count) {
        arr[count-1].init(count);
    }
}

int main(int argc, char* argv[])
{
    std::cout << "Calling new[10], 20 ctors called" << std::endl;
    Child* arr = new Child[10]; // 20 ctors called;
    // will print the first element because of Base::operator<<
    std::cout << "0: " << *arr << std::endl;
    set(arr, 10);
    print(arr, arr+10);
    std::cout << "0: " << *arr << std::endl;
    std::cout << "Calling delete[], 20 dtors called" << std::endl;
    delete[] arr; // 20 dtors called;

    std::cout << "Calling malloc(sizeof*10), 0 ctors called" << std::endl;    
    arr = static_cast<Child*>(std::malloc(sizeof(Child)*10)); // no ctors
    std::cout << "The next line will seg-fault" << std::endl;
    // Segfault because the base pointers were never initialized
    std::cout << "0: " << *arr << std::endl; // segfault
    set(arr, 10);
    print(arr, arr+10);
    std::cout << "0: " << *arr << std::endl;
    std::cout << "Calling free(), 0 dtors called" << std::endl;
    free(arr); // no dtors

    return 0;
}

The above code is compliant and compiles without error on g++ and Visual Studio, but due to the inheritance, both crash when I try to print the first element after a malloc (because the base class was never initialized).

So you can indeed create and delete an array of objects without calling their constructors and destructors, but doing so results in a slew of extra scenarios you need to be aware of and account for to avoid undefined behavior or crashes, and if this is the case for your code, such that you need to ensure the destructors are not called, you might want to reconsider your overall design (possibly even use an STL container or smart pointer types).

Hope that can help.

txtechhelp
  • 6,147
  • 1
  • 27
  • 36