Advertisement

C++ pointers that are released upon going out of scope

Started by February 13, 2025 05:02 PM
49 comments, last by taby 3 days, 3 hours ago

OK, here's my code so far. How do I pass it all into a function that uses char**?

#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;


// Works good for a vector<char>
void test_function(const char* c, const size_t size_x, const size_t size_y)
{
	for (size_t i = 0; i < size_x; i++)
	{
		for (size_t j = 0; j < size_y; j++)
		{
			size_t index = i * (size_y) + j;
			cout << (int)c[index] << endl;
		}
	}
}

// How to get it to call this function?
void test_function(const char** c, const size_t size_x, const size_t size_y)
{

}


int main(void)
{
	const size_t size_x = 2;
	const size_t size_y = 2;
	const size_t size_z = 3;

	//vector<vector<vector<int>>> data3(size_x, vector<vector<int>>(size_y, vector<int>(size_z)));
	//test_function(get_pointer_from_vector(data3, size_x, size_y), size_x, size_y, size_z);

	//vector<vector<char>> data2(size_x, vector<char>(size_y));

	vector<char> data2_(size_x * size_y);
	test_function(data2_.data(), size_x, size_y);



	//test_function(get_pointer_from_vector(data2, size_x), size_x, size_y);

	//vector<double> data1(size_x);
	//test_function(get_pointer_from_vector(data1), size_x);

	return 0;
}

OK, I figured it out, sort of.

#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;


// Works good for a vector<char>	
void test_function(const char* c, const size_t size_x, const size_t size_y)
{
	for (size_t i = 0; i < size_x; i++)
	{
		for (size_t j = 0; j < size_y; j++)
		{
			size_t index = j * (size_x) + i;
			cout << (int)c[index] << endl;
		}
	}
}

void test_function2(const char** c, const size_t size_x)
{
	for (size_t i = 0; i < size_x; i++)
	{
		cout << (int)c[0][i] << ' ';
	}
}


int main(void)
{
	const size_t size_x = 2;
	const size_t size_y = 2;
	const size_t size_z = 2;

	//vector<vector<vector<int>>> data3(size_x, vector<vector<int>>(size_y, vector<int>(size_z)));
	//test_function(get_pointer_from_vector(data3, size_x, size_y), size_x, size_y, size_z);

	//vector<vector<char>> data2(size_x, vector<char>(size_y));

	vector<char> data2 = {1, 2, 3, 4};// (size_x * size_y);
	test_function(&data2[0], size_x, size_y);
	
	size_t row_number = 1;

	test_function2(reinterpret_cast<const char**>(&data2[row_number * size_x]), size_x);



	//test_function(get_pointer_from_vector(data2, size_x), size_x, size_y);

	//vector<double> data1(size_x);
	//test_function(get_pointer_from_vector(data1), size_x);

	return 0;
}

Advertisement

make data2 the expected type.

both of these will call that second function also.


const char* other[] = {"something dumb", "something dumber"}; 
test_function(other, size_x, size_y);

vector<const char*> data2(size_x * size_y);
test_function(&data2[0], size_x, size_y); // nothing strings

Dev careful. Pixel on board.
Buckle up. Everything will be revealed.

Better:

#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;


template<class T>
void test_function2(const T* c, const size_t size_x)
{
	for (size_t i = 0; i < size_x; i++)
	{
		if constexpr (is_same<T, char>::value)
			cout << (int)c[i] << ' ';
		else
			cout << c[i] << ' ';
	}
}

template<class T>
void test_function2(const T** c, const size_t size_x)
{
	for (size_t i = 0; i < size_x; i++)
	{
		if constexpr (is_same<T, char>::value)
			cout << (int)c[0][i] << ' ';
		else
			cout << c[0][i] << ' ';
	}
}

template<class T>
void test_function2(const T*** c, const size_t size_x)
{
	for (size_t i = 0; i < size_x; i++)
	{
		if constexpr (std::is_same<T, char>::value)
			cout << (int)c[0][0][i] << ' ';
		else
			cout << c[0][0][i] << ' ';
	}
}


int main(void)
{
	const size_t size_x = 2;
	const size_t size_y = 2;
	const size_t size_z = 2;

	vector<char> data2(size_x * size_y * size_z);

	for (size_t i = 0; i < size_x; i++)
	{
		for (size_t j = 0; j < size_y; j++)
		{
			for (size_t k = 0; k < size_z; k++)
			{
				size_t index = i * (size_y * size_z) + j * size_z + k;

				data2[index] = char(i + j + k);

				cout << (int)data2[index] << ' ';
			}

			cout << endl;
		}

		cout << endl;
	}
	
	cout << endl;

	size_t row_number = 1;
	size_t column_number = 1;

	const char* ptr1 = &data2[row_number * size_x + column_number * size_x * size_y];
	const char** ptr2 = &ptr1;

	test_function2(&ptr2, size_x);

	return 0;
}

I am stumped. It's allocating float* variables, but I cannot seem to convert this to float.

threadprobs_[index0] = probs[0] + (size_t)(threadstart - start) * num_samples_use;
                    
threadprobs_[index1] = probs[1] + (size_t)(threadstart - start) * num_samples_use;

The whole code for the function is:


int read_data_fly(char* datafile, int dtype, double* data, float** probs,
    int num_samples_use, int* keepsamps, int start, int end,
    int* keeppreds_use, gzFile inputgz, size_t current,
    int num_samples, int num_preds, int genskip, int genheaders,
    int genprobs, size_t* bgen_indexes, double missingvalue,
    double threshold, double minprob, int nonsnp,
    int maxthreads) 
{
    int  thread;
    int  threadstart;
    int  threadend;
    int  threadlength;
 //   float*** threadprobs;

    vector<float> threadprobs_;


    if (dtype == 1 || dtype == 2 || dtype == 3 ||
        dtype == 4) // can read in parallel
    {
        threadlength = (end - start - 1) / maxthreads + 1;
        if (dtype == 2) {

            //threadprobs = malloc(sizeof(float**) * maxthreads);
        
            threadprobs_.resize(maxthreads);
        }

#pragma omp parallel for private(thread, threadstart, threadend)   schedule(static, 1)


        for (thread = 0; thread < maxthreads; thread++) {
            ;
            threadstart = start + thread * threadlength;
            threadend = start + (thread + 1) * threadlength;
            if (threadend > end) {
                threadend = end;
            }

            if (dtype == 1) {
                read_bed_fly(
                    datafile, data + (size_t)(threadstart - start) * num_samples_use,
                    num_samples_use, keepsamps, threadend - threadstart,
                    keeppreds_use + threadstart, num_samples, num_preds, missingvalue);
            }

            if (dtype == 2) {
                if (probs == NULL) {
                    read_bgen_fly(datafile,
                        data + (size_t)(threadstart - start) * num_samples_use,
                        NULL, num_samples_use, keepsamps, threadstart,
                        threadend, keeppreds_use, num_samples, num_preds,
                        bgen_indexes, missingvalue, threshold, minprob);
                }
                else {
                    //threadprobs[thread] = malloc(sizeof(float*) * 2);
                   
                    size_t index0 = thread * threadprobs_.size() + 0;
                    size_t index1 = thread * threadprobs_.size() + 1;

                    threadprobs_.resize(threadprobs_.size() * 2);

                    //threadprobs_[thread][0] =
                    //    probs[0] + (size_t)(threadstart - start) * num_samples_use;
                    //
                    //threadprobs_[thread][1] =
                    //    probs[1] + (size_t)(threadstart - start) * num_samples_use;


                    const float* ptr1 = &threadprobs_[0];// +column_number * size_x * size_y];
                    const float** ptr2 = &ptr1;

                    // These fail because the righthand side is a float*
                    threadprobs_[index0] = probs[0] + (size_t)(threadstart - start) * num_samples_use;
                    threadprobs_[index1] = probs[1] + (size_t)(threadstart - start) * num_samples_use;



                    //read_bgen_fly(
                    //    datafile, data + (size_t)(threadstart - start) * num_samples_use,
                    //    threadprobs_[index0], num_samples_use, keepsamps, threadstart,
                    //    threadend, keeppreds_use, num_samples, num_preds, bgen_indexes,
                    //    missingvalue, threshold, minprob);

                    read_bgen_fly(
                        datafile, data + (size_t)(threadstart - start) * num_samples_use,
                        ptr2, num_samples_use, keepsamps, threadstart,
                        threadend, keeppreds_use, num_samples, num_preds, bgen_indexes,
                        missingvalue, threshold, minprob);
                    
                    //free(threadprobs[thread]);
                }
            }

            if (dtype == 3) {
                read_sped_fly(
                    datafile, data + (size_t)(threadstart - start) * num_samples_use,
                    num_samples_use, keepsamps, threadstart, threadend, keeppreds_use,
                    num_samples, num_preds, missingvalue, threshold, nonsnp);
            }

            if (dtype == 4) {
                read_speed_fly(
                    datafile, data + (size_t)(threadstart - start) * num_samples_use,
                    num_samples_use, keepsamps, threadstart, threadend, keeppreds_use,
                    num_samples, num_preds, missingvalue, threshold, nonsnp);
            }
        }

        if (dtype == 2) {
           // free(threadprobs);
        }
    }

    if (dtype == 5) {
        (void)read_gen_fly(datafile, data, probs, num_samples_use, keepsamps, start,
            end, keeppreds_use, inputgz, current, num_samples,
            num_preds, genskip, genheaders, genprobs, missingvalue,
            threshold, minprob, nonsnp);
    }

    return (current + end);
}

Is there a better way, using make_unique?

Advertisement

I am wondering if a simple class for wrapping memory management wouldn't be simpler.

Make a class with fields for allocated data. Allocate the data in the constructor, and release it again in the destructor. The class doesn't need to do anything else.

The constructor gets called when you create an instance, the destructor gets called when it goes out of scope. Use printf to confirm the constructor/destructor is being executed if you want.

While it uses the calls you want to avoid, it's just in this class, and it's not too difficult to ensure you release everything you allocated in that single class.

That’s brilliant. Is there any chance that you could provide a code sample?

I found a code by saying “C++ malloc wrapper class that automaticallly calls free" to Claude AI.

#include <cstdlib>
#include <stdexcept>
#include <utility>

template <typename T>
class MallocWrapper {
private:
    T* ptr;
    size_t count;

public:
    // Constructor for single object
    MallocWrapper() : ptr(static_cast<T*>(malloc(sizeof(T)))), count(1) {
        if (!ptr) {
            throw std::bad_alloc();
        }
    }

    // Constructor for array
    explicit MallocWrapper(size_t elements) : 
        ptr(static_cast<T*>(malloc(sizeof(T) * elements))),
        count(elements) {
        if (!ptr) {
            throw std::bad_alloc();
        }
    }

    // Destructor
    ~MallocWrapper() {
        free(ptr);
    }

    // Delete copy constructor and assignment operator
    MallocWrapper(const MallocWrapper&) = delete;
    MallocWrapper& operator=(const MallocWrapper&) = delete;

    // Move constructor
    MallocWrapper(MallocWrapper&& other) noexcept : 
        ptr(other.ptr),
        count(other.count) {
        other.ptr = nullptr;
        other.count = 0;
    }

    // Move assignment operator
    MallocWrapper& operator=(MallocWrapper&& other) noexcept {
        if (this != &other) {
            free(ptr);
            ptr = other.ptr;
            count = other.count;
            other.ptr = nullptr;
            other.count = 0;
        }
        return *this;
    }

    // Get raw pointer
    T* get() noexcept {
        return ptr;
    }

    // Get const raw pointer
    const T* get() const noexcept {
        return ptr;
    }

    // Array access operator
    T& operator[](size_t index) {
        if (index >= count) {
            throw std::out_of_range("Index out of bounds");
        }
        return ptr[index];
    }

    // Const array access operator
    const T& operator[](size_t index) const {
        if (index >= count) {
            throw std::out_of_range("Index out of bounds");
        }
        return ptr[index];
    }

    // Get number of elements
    size_t size() const noexcept {
        return count;
    }

    // Check if pointer is null
    bool is_null() const noexcept {
        return ptr == nullptr;
    }
};
// Single object allocation
MallocWrapper<int> single;
*single.get() = 42;

// Array allocation
MallocWrapper<double> array(10);
array[0] = 3.14;

// Move semantics
MallocWrapper<char> str1(5);
MallocWrapper<char> str2 = std::move(str1); // str1 is now null

P.S. What is the advantage of using a malloc wrapper, versus using a vector? Syntactically, they're pretty much identical. Works good either way, so thank you anyway.

This feels like he worst possible way to accomplish the goal, and a rough way to learn through guessing what AI is trying to do rather than starting with study and understanding, then building to suit based on that understanding.

Go read some good books teaching the fundamentals, like Accelerated C++ by Koenig and Moo, or the C++ Primer (5th Edition) by Lippman, La Joie, and Moo. I don't know what exactly is missing, but there seems to be a pretty big gap in your understanding of the fundamentals of memory management and object lifetimes.

While it is certainly possible to keep guessing, and relying on AI to fill in the crutch as you try to figure it out that's not going to help you architect larger and larger solutions over time.

As for the advantage, both are pretty bad. Using a std::vector conveys intent: you're using a built-in container and then not actually using it as a container, you're using a container as a proxy for RAII. Building your own wrapper implements RAII but does it seemingly needlessly to avoid calls; there's no reason to build it that way, simply using it because you are arbitrarily deciding you don't like a type of memory management, so you're building a different kind of memory management to hide it for limited functional gain.

Advertisement