Error: unique_ptr constructed with null deleter pointer

Started by
8 comments, last by blueshogun96 9 years, 5 months ago

I have this really annoying error I can't fix. Spend hours trying to find a solution, nothing yet. I've tried using a handful of methods to fix it, but nothing has worked. Here's what I've tried:

original:


std::unique_ptr<T, void(*)(void*)> p( resource, delete_func );
m_map[id] = std::move(p);

2nd attempt:


m_map.emplace( 1, std::unique_ptr<T, void(*)(void*)>( resource, delete_func ) );

3rd attempt:


m_map.insert( std::unordered_map<uint64_t, std::unique_ptr<T, void(*)(void*)>>::value_type( 1, std::unique_ptr<T, void(*)(void*)>( resource, delete_func ) ) );

I end up with the same error every time! What is going on here? Here's the full definition of my class:


#include <iostream>
#include <vector>
#include <map>
#include <unordered_map>
#include <memory>
#include "str_id.hpp"

/*
 * Resource management class
 */
template <class T>
class resource_manager_t
{
public:
	resource_manager_t() {};
	virtual ~resource_manager_t() {};

public:
	void add_resource( char* resource_id, T* resource, void (*delete_func)(void*) )
	{
		/* Add this resource to the list */
		m_strids.add_cstr( resource_id );
		uint64_t id = m_strids.get_cstr_id( resource_id );

		m_map.insert( std::unordered_map<uint64_t, std::unique_ptr<T, void(*)(void*)>>::value_type( 1, std::unique_ptr<T, void(*)(void*)>( resource, delete_func ) ) );
	}

	T* get_resource( char* resource_id )
	{
		uint64_t id = m_strids.get_cstr_id( resource_id );

		if( id != 0 )
		{
			return m_map[id].get();
		}

		return nullptr;
	}

private:
	std::unordered_map<uint64_t, std::unique_ptr<T, void(*)(void*)>> m_map;
	cstr_id_manager_t						m_strids;
};

At this point I'm completely out of ideas. This sucks. Anyone else have any ideas? Thanks.

Shogun.

EDIT: The title of this thread is the error message I'm getting, but if the more detailed version of it helps, here it is:

1>c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(1347): error C2338: unique_ptr constructed with null deleter pointer
1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(1343) : while compiling class template member function 'std::unique_ptr<T,void (__cdecl *)(void *)>::unique_ptr(void) throw()'
1> with
1> [
1> T=int
1> ]
1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\tuple(746) : see reference to function template instantiation 'std::unique_ptr<T,void (__cdecl *)(void *)>::unique_ptr(void) throw()' being compiled
1> with
1> [
1> T=int
1> ]
1> c:\shogun3d\kaho\source\resource_manager.h(29) : see reference to class template instantiation 'std::unique_ptr<T,void (__cdecl *)(void *)>' being compiled
1> with
1> [
1> T=int
1> ]
1> c:\shogun3d\kaho\source\resource_manager.h(22) : while compiling class template member function 'void resource_manager_t<int>::add_resource(char *,T *,void (__cdecl *)(void *))'
1> with
1> [
1> T=int
1> ]
1> c:\shogun3d\kaho\source\game.cpp(19) : see reference to function template instantiation 'void resource_manager_t<int>::add_resource(char *,T *,void (__cdecl *)(void *))' being compiled
1> with
1> [
1> T=int
1> ]
1> c:\shogun3d\kaho\source\game.cpp(16) : see reference to class template instantiation 'resource_manager_t<int>' being compiled

Advertisement

Putting the actual error into the post would be rather helpful.

On thing that I find suspicious:


void(*)(void*)

why is your delete function declared that way? you have the resource type via template, so why not


void(*)(T*)

even if its not related to the error, that would at least be more straight-forward and type-safe, wouldn't it...

EDIT: Although I don't know std::unique_ptr too well, and if it actually requires the void* as argument of the deleter function, my apologies.

Without seeing either the error message you're trying to get help with or the code causing it (why do you think you will get useful help if you describe neither the problem nor the cause?), my guess is you're likely invoking undefined behaviour.

If you look at the standard, it says this.

Type requirements
- Deleter must be FunctionObject or lvalue reference to a FunctionObject or lvalue reference to function, callable with an argument of type unique_ptr<T, Deleter>::pointer

That means what Juliean says above is likely the cause of whatever mysterious problem you're encountering. Without seeing the definition of your delete_func it's hard to say, but I would expect any quality C++ implementation to give a compile failure if your code does not conform to the "must" requirements in the standard.

Stephen M. Webb
Professional Free Software Developer

Putting the actual error into the post would be rather helpful.

The exact error is the title of the thread. Next time, I won't do that to avoid confusion. Better yet, I'll share the entire output that Visual Studio gives me above.

Without seeing either the error message you're trying to get help with or the code causing it (why do you think you will get useful help if you describe neither the problem nor the cause?), my guess is you're likely invoking undefined behaviour.

If you look at the standard, it says this.

Type requirements
- Deleter must be FunctionObject or lvalue reference to a FunctionObject or lvalue reference to function, callable with an argument of type unique_ptr<T, Deleter>::pointer

That means what Juliean says above is likely the cause of whatever mysterious problem you're encountering. Without seeing the definition of your delete_func it's hard to say, but I would expect any quality C++ implementation to give a compile failure if your code does not conform to the "must" requirements in the standard.

Once again, the title is the error message Visual Studio gives me. The compiler complains that there's no deleter being set when I have clearly set one.

On thing that I find suspicious:


void(*)(void*)

why is your delete function declared that way? you have the resource type via template, so why not


void(*)(T*)

even if its not related to the error, that would at least be more straight-forward and type-safe, wouldn't it...

EDIT: Although I don't know std::unique_ptr too well, and if it actually requires the void* as argument of the deleter function, my apologies.

This isn't required, nor is it the error. Not necessarily a bad idea, but right now, I just want to get things to work, and the example deleter below works fine (it's how I initially learned to use the custom deletion function).


template <class type>
void kaho_game_delete_impl( void* ptr )
{
	static_cast<type*>( ptr )->uninitialize();
	delete static_cast<type*>( ptr );
}

I've used this deleter and similar ones before. I never had any problems until using it in conjunction with std::unordered_map.

Shogun.

EDIT: This is an example of what I'm trying to do.


template <class type>
void test_delete_impl( void* ptr )
{
	delete static_cast<type*>( ptr );
}

m_rm_test = new resource_manager_t<int>();
int* i = new int; *i = 5;

m_rm_test->add_resource( "test", i, test_delete_impl<int> );

The deleter does indeed work, whether you're using type* or void* it doesn't really matter, so I'm 98% sure that's not the problem. Let me show you an example of my custom deletion that actually DOES work.


#include "game.h"


/*
 * Game class deletion function
 */
template <class type>
void kaho_game_delete_impl( void* ptr )
{
	static_cast<type*>( ptr )->uninitialize();
	delete static_cast<type*>( ptr );
}

/* 
 * Main function 
 */
int main( int argc, char** argv )
{
	std::unique_ptr<kaho_game_t, void(*)(void*)> game( new kaho_game_t, kaho_game_delete_impl<kaho_game_t> );

	if( !game->initialize() )
		return -1;

	return game->main_loop();
}

I'm quite convinced that the issue lies with std::unordered_map somehow. This post on stack overflow illustrates the exact same problem, only I'm using std::unordered_map instead of std::map

http://stackoverflow.com/questions/19207999/why-can-i-not-create-this-unique-ptr-with-deleter

Hopefully this will be enough information as to what my problem is.

The most useful thing you could produce would be a tiny complete program that reproduces the problem. We can then be sure that you haven't left out any important detail, and we can try to reproduce the problem. It's also very likely you will find a mistake in your code in the process of producing that tiny program.

Since the error is about the default constructor, I'd blame it on the map creating a default constructed object if you call the [] operator with a new key. Are you getting the same error for simpler containers like a vector or if you try using a shared_ptr? Maybe some detail about the operator [] implementation is screwing you over and tries to copy a default constructed unique_ptr?

emplace is definitely more likely to work than insert. Does the error move somewhere else if you comment out the code in add_resource? If not, the problem really seems to be limited to adding it the proper way. If it does, the problem is probably more fundamental than that.

f@dzhttp://festini.device-zero.de

The most useful thing you could produce would be a tiny complete program that reproduces the problem. We can then be sure that you haven't left out any important detail, and we can try to reproduce the problem. It's also very likely you will find a mistake in your code in the process of producing that tiny program.

Good idea. Should have done this before. This is what I wrote, and I'll explain more in a second:


#include <iostream>
#include <unordered_map>
#include <memory>


template <class T>
class my_list
{
public:
	my_list() : m_counter(0) {};
	~my_list() {};

	uint64_t add( T* resource, void( *delete_func )( T* ) )
	{
		//std::unique_ptr<T, void(*)(T*)> p( resource, delete_func );
		//m_map[m_counter++] = std::move(p);
		m_map.emplace( m_counter++, std::unique_ptr<T, void(*)(T*)>( resource, delete_func ) );

		return m_counter;
	}

	T* get( uint64_t id )
	{
		return m_map[id].get();
	}
private:
	std::unordered_map<uint64_t, std::unique_ptr<T, void(*)(T*)>> m_map;
	uint64_t m_counter;
};

template <class type>
void delete_impl( type* ptr )
{
	delete ptr;
}

int main( int argc, char** argv )
{
	my_list<int> list;
	
	uint64_t id = list.add( new int, delete_impl<int> );

	int* i = list.get(id);

	std::cout << "Value is: " << *i << std::endl;

	return 0;
}

After tinkering with this test program, I have good news and bad news.

Bad: I get the exact same error.

Good: I got this compiled and running at one point, but that was because I had used "return nullptr" in get() for the sake of stubbing it temporarily. I forgot to change it, but it worked for a moment. Then I change it to "return m_map[id].get()" and I get the error again.

I used the template as the function pointer parameter so people can stop assuming that's the problem.

Since the error is about the default constructor, I'd blame it on the map creating a default constructed object if you call the [] operator with a new key. Are you getting the same error for simpler containers like a vector or if you try using a shared_ptr? Maybe some detail about the operator [] implementation is screwing you over and tries to copy a default constructed unique_ptr?

emplace is definitely more likely to work than insert. Does the error move somewhere else if you comment out the code in add_resource? If not, the problem really seems to be limited to adding it the proper way. If it does, the problem is probably more fundamental than that.

Okay, now those are some VERY good questions. I mentioned this above in response to Alvaro, that the m_map[id].get() appears to be causing it. No idea why or how to get around it. Without this line of code, the error appears to disappear.

Now we're getting somewhere. Oh yeah, I haven't tried using a std::vector, nor have I tried using std::shared_ptr. I hear that shared_ptr is a bit more expensive, so I defaulted to unique_ptr. If I can't get this working, I'll likely just switch to one of the two mentioned and see if that works.

Shogun.

The index operator on maps (ordered and unordered varieties) require that the object be default constructible. If the object is not found in the map then it will default construct one and return a reference to it.

Since std::unique_ptr cannot be default constructed, you get an error. Use the find() member function which will return an iterator to the element (if it exists, end() otherwise). You can then dereference it to get an std::pair<Key, Value> of which the .second member is the object you are attempting to call get on.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

After a few minutes of trial and error over the chat with Washu, finally got it working.


#include <iostream>
#include <unordered_map>
#include <memory>


template <class T>
class my_list
{
public:
	my_list() : m_counter(0) {};
	~my_list() {};

	uint64_t add( T* resource, void( *delete_func )( T* ) )
	{
		//std::unique_ptr<T, void(*)(T*)> p( resource, delete_func );
		//m_map[m_counter++] = std::move(p);
		m_map.emplace( ++m_counter, std::unique_ptr<T, void(*)(T*)>( resource, delete_func ) );

		return m_counter;
	}

	T* get( uint64_t id )
	{
		auto itor = m_map.find( id );

		if( itor == m_map.end() )
			return nullptr;

		return itor->second.get();
	}
private:
	std::unordered_map<uint64_t, std::unique_ptr<T, void(*)(T*)>> m_map;
	uint64_t m_counter;
};

template <class type>
void delete_impl( type* ptr )
{
	delete ptr;
}

int main( int argc, char** argv )
{
	my_list<int> list;
	
	int* int_ptr = new int(5);
	uint64_t id = list.add( int_ptr, delete_impl<int> );

	int* i = list.get(id);

	std::cout << int_ptr << std::endl << i << std::endl;
	std::cout << "Value is: " << *i << std::endl;

	return 0;
}

Tested this out. No fuss, no leaks, no BS. Washu nailed it in his response.

Thanks everybody, I appreciate it! smile.png

Shogun.

This topic is closed to new replies.

Advertisement