How to determine whether an object has heap allocated members?

Viewed 441

Is there a way to determine whether an object has at least one of its members on the heap?

I am trying to be able to distinguish objects like std::string or std::vector or std::list (yeah mostly containers) from all other types (unfortunately even containers with a single element are within my "scope of interest")

I am trying to do something like the following:

struct foo
{
private:
    int * _ptr;

public:
    foo() : _ptr(new int) {};
    ~foo() { delete _ptr; };
};

struct bar
{
private:
    int val;
};

template <typename T>
void func(T val)
{
    if constexpr (std::is_class_v<T>)
    {
        std::cout << std::setw(20) << typeid(T).name() << " is a class type." << std::endl;
        if (/* determine if foo has any heap allocations */)
        {
            // Do something #1.
            std::cout << std::setw(20) << typeid(T).name() << " does allocate on heap." << std::endl;
        }
        else
        {
            // Do something #2.
            std::cout << std::setw(20) << typeid(T).name() << " does NOT allocate on heap." << std::endl;
        }
    }
    else
    {
        // Do something #3.
        std::cout << std::setw(20) << typeid(T).name() << " is NOT a class type." << std::endl;
    }
}

int main()
{
    func(foo()); // foo does allocate on heap
    cout << endl;

    func(bar()); // bar does NOT allocate on heap
    cout << endl;
};

classes foo and bar are just examples, and the function func() has to perform functionality a bit different than couting to console.

5 Answers

This is XY problem... I am trying to write a hash function that will be aware whether the user-defined key type stores any data on an array (just like std::string` would). I am fairly able to create a generic hash function that takes any type, but if that type has an array (a pointer) to some of it's stored data, problems become very tricky (pointers to dynamic arrays change at runtime, the same key at, different times will produce different hash value)

Be aware that some structure/class types have internal uninitialized padding, which may make your hash function work incorrectly.

Otherwise, std::is_trivially_copyable would be a good start.

As others pointed out in the comments, there is no way to distinguish whether the pointer pointing to dynamically allocated or not. On the other hand, C++ standard <type_traits> provides type traits like

and so on. Have a look at to the links, whether it suits your actual problem.


That being said, why it can't be solved with a member function which tells the information what you need?

For example:

struct foo
{
public:
    bool isHeapAllocated() const noexcept { return true; }
};

struct bar
{
public:
    bool isHeapAllocated() const noexcept { return false; }
};

Then simply call the function:

template <typename T> void func(T val)
{
    if constexpr (std::is_class_v<T>)
    {
        if (val.isHeapAllocated()) { 
            // Do something #1.
        }
        else
        {
            // Do something #2.
        }
    }
    else
    {
        // Do something #3.
    }
}

While I am pretty sure the other answers are right when they say you shouldn't mess with trying to determine whether an object lives on heap or stack I have to disagree, that it is "impossible" to determine.

The following example uses windows (and the procedure will be different for every OS!):

#include <Windows.h>
bool isAddressOnHeap(void* address) {
    bool returnval = 0;
    PROCESS_HEAP_ENTRY entry;
    HANDLE procHeap = GetProcessHeap();
    if (procHeap == NULL) {
        std::cerr << "Failed to retrieve Heap.\n";
        return -1;
    }

    if (HeapLock(procHeap) == FALSE) {
        std::cerr << "Failed to lock heap.\n";
        return -1;
    }

    entry.lpData = NULL;
    if (HeapWalk(procHeap, &entry) != 0) {
        if (entry.wFlags & PROCESS_HEAP_REGION) {
            std::cout << "Function succeeded. The heap begins at " << (void*)entry.Region.lpFirstBlock << " and ends at " << (void*)entry.Region.lpLastBlock << "\n";
            if (address >= (void*)entry.Region.lpFirstBlock && address <= (void*)entry.Region.lpLastBlock) 
                returnval = 1;
        }
    }

    if (HeapUnlock(procHeap) == FALSE) {
        std::cerr << "Failed to unlock heap.\n";
        return -1;
    }
    return returnval;
}

This function will determine, whether a given address is on heap or not. Here the test program:

int main()
{
    int i = 1;
    std::cout << "Address of i on Stack: " << (void*)&i << "\n";


    int* j = new int;
    *j = 1;
    std::cout << "Address of j on Heap: " << (void*)j << "\n";

    int* k = (int*)malloc(sizeof(int));
    *k = 1;
    std::cout << "Address of k on Heap: " << (void*)k << "\n";


    std::cout << "is i on Heap?: " << isAddressOnHeap(&i) << "\n";
    std::cout << "is j on Heap?: " << isAddressOnHeap(j) << "\n";
    std::cout << "is k on Heap?: " << isAddressOnHeap(k) << "\n";

    delete j;
    free(k);
    return 0;
}

The program returns the following on my machine:

Address of i on Stack: 0000001062AFFE80
Address of j on Heap: 0000018B11C530B0
Address of k on Heap: 0000018B11C53070
Function succeeded. The heap begins at 0000018B11C40740 and ends at 0000018B11D3F000
is i on Heap?: 0
Function succeeded. The heap begins at 0000018B11C40740 and ends at 0000018B11D3F000
is j on Heap?: 1
Function succeeded. The heap begins at 0000018B11C40740 and ends at 0000018B11D3F000
is k on Heap?: 1

You can read more about how this code works on Microsoft's website.

On Linux machines there seems to be similar ways to do this.

But as a last word of warning: This answer is just a proof of concept!

I am trying to distinguish between "class type with an array as a member" and a "class type without an array as a member".

You need to look at the code for that, so you'll need pass your function the path to the source code and you'll need to parse it.

The only sure way is to add the needed machinery to handle each type, and as necessary individual object.

The following is a good start for any sane types (respecting the rule of zero / three / five):

template <class T>
constexpr auto owns_no_dynamic_memory(T const& x) noexcept {
    if constexpr (std::is_trivially_copyable_v<T>
    || std::is_trivially_move_constructible_v<T>
    || std::is_trivially_copy_assignable_v<T>
    || std::is_trivially_move_assignable_v<T>
    || std::is_trivially_destructible_v<T>)
        return std::true_type();
    else
        return false;
}

Add overloads as needed.

Related