Consider the following piece of code:
int three() {
return 3;
}
template <typename T>
class Foo {
private:
T* ptr;
public:
void bar(T& t) { ptr = new T(t); }
void bar(const T& t) { ptr = new T(t); }
void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
};
int main() {
Foo<int> foo;
int a = 3;
const int b = 3;
foo.bar(a); // <--- Calls Foo::bar(T& t)
foo.bar(b); // <--- Calls Foo::bar(const T& t)
foo.bar(three()); // <--- Calls Foo::bar(T&& t); Runs fine, but only if either of the other two are called first!
return 0;
}
My question is, why does the third overload Foo::bar(T&& t) crash the program? What exactly is happening here? Does the parameter t get destroyed after the function returns?
Furthermore, let's assume that the template parameter T was a very large object with a very costly copy constructor. Is there any way to use RValue References to assign it to Foo::ptr without directly accessing this pointer and making a copy?
In this line
void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
you can dereference an uninitialized pointer. This is undefined behavior.
You must call one of the two other version of bar first because you need to create the memory for your object.
So I would do ptr = new T(std::move(t));.
If your type T supports moving the move constructor will get called.
Update
I would suggest something like that. Not sure if you need the pointer type within foo:
template <typename T>
class Foo {
private:
T obj;
public:
void bar(T& t) { obj = t; } // assignment
void bar(const T& t) { obj = t; } // assignment
void bar(T&& t) { obj = std::move(t); } // move assign
};
This would avoid memory leaks which are also quite easy with your approach.
If you really need the pointer in your class foo how about that:
template <typename T>
class Foo {
private:
T* ptr;
public:
Foo():ptr(nullptr){}
~Foo(){delete ptr;}
void bar(T& t) {
if(ptr)
(*ptr) = t;
else
ptr = new T(t);
}
void bar(const T& t) {
if(ptr)
(*ptr) = t;
else
ptr = new T(t);
}
void bar(T&& t) {
if(ptr)
(*ptr) = std::move(t);
else
ptr = new T(std::move(t));
}
};
There's no reason for that to fail in that code. ptr will point to an existing int object created by the previous calls to bar and the third overload will just assign the new value to that object.
However, if you did this instead:
int main() {
Foo<int> foo;
int a = 3;
const int b = 3;
foo.bar(three()); // <--- UB
return 0;
}
That foo.bar(three()); line would have undefined behaviour (which does not imply any exception), because ptr would not be a valid pointer to an int object.
Assuming that you only called foo.bar(three()); without the other two calls:
Why did you think that'd work? Your code is essentially equivalent to this:
int * p;
*p = 3;
That's undefined behaviour, because p isn't pointing to a valid variable of type int.
The "unsafe"thing, here is that, before assigning to ptr a new object, you should worry about the destiny of what ptr actually points to.
foo.bar(three());
is unsafe in the sense that you have to grant -before calling it- that ptr actually point to something. In your case it points to what was created by foo.bar(b);
But foobar(b) makes ptr to point to a new object forgetting the one created by foobar(a)
A more proper code can be
template<class T>
class Foo
{
T* p;
public:
Foo() :p() {}
~Foo() { delete p; }
void bar(T& t) { delete p; ptr = new T(t); }
void bar(const T& t) { delete p; ptr = new T(t); }
void bar(T&& t)
{
if(!ptr) ptr = new T(std::move(t));
else (*ptr) = std::move(t);
}
}
;
Related
I have two classes, foo and bar, where bar contains a pointer to foo, as below.
#include<iostream>
#include<memory>
class foo {
private:
int num{4};
public:
void sum(const int& to_add) {num += to_add;}
int access_num() {return num;}
};
class bar {
private:
std::shared_ptr<foo> ptr;
public:
void change_ptr(foo& f) {
auto new_ptr = std::make_shared<foo>(f);
ptr = std::move(new_ptr);
}
std::shared_ptr<foo> access_ptr() { return ptr; }
};
If I want to execute the member function sum() of foo via the pointer in bar, how do I do it?
Currently, trying
foo f;
std::shared_ptr<bar> bar_ptr = std::make_shared<bar>();
bar_ptr->change_ptr(f);
// Add three to the int stored in f via the pointer
bar_ptr->access_ptr()->sum(3);
std::cout << f.access_num() << std::endl;
does not work, outputting 4.
This code
void change_ptr(foo& f) {
auto new_ptr = std::make_shared<foo>(f); // copy constructor
ptr = std::move(new_ptr);
}
calls a copy constructor to create an instance of foo on the heap and manage it with std::shared_ptr. You can check it if you delete the copy constructor in foo declaration. The code won't compile. Note that you'll have to provide at least a default constructor since the rule of zero doesn't work if you have explicitly deleted copy constructor.
class foo {
private:
int num{4};
public:
foo() = default;
foo(foo const &other) = delete;
void sum(const int& to_add) {num += to_add;}
int access_num() {return num;}
};
foo f is an automatic variable stored on the stack. There is no much use to manage it with std::shared_ptr. What you probably need is to create an instance of foo on the heap and use std::shared_ptr's to work with it:
#include<memory>
class foo {
private:
int num{4};
public:
void sum(const int& to_add) {num += to_add;}
int access_num() {return num;}
};
class bar {
private:
std::shared_ptr<foo> ptr;
public:
void change_ptr(std::shared_ptr<foo> f) {
ptr = std::move(f); // f is already a copy, so we can safely move it
}
std::shared_ptr<foo> access_ptr() { return ptr; }
};
int main() {
auto f = std::make_shared<foo>();
std::shared_ptr<bar> bar_ptr = std::make_shared<bar>();
bar_ptr->change_ptr(f);
bar_ptr->access_ptr()->sum(3);
std::cout << f->access_num() << std::endl;
}
I have heard people say that "C++ doesn't need placement delete because it wouldn't do anything."
Consider the following code:
#include <cstdlib>
#include <cstdio>
#include <new>
////////////////////////////////////////////////////////////////
template<typename T, typename... ARGS>
T* customNew1(ARGS&&... args) {
printf("customNew1...\n");
auto ret = new T { std::forward<ARGS>(args)... };
printf("OK\n\n");
return ret;
}
template<typename T>
void customDelete1(T *ptr) {
printf("customDelete1...\n");
delete ptr;
printf("OK\n\n");
}
////////////////////////////////
template<typename T, typename... ARGS>
T* customNew2(ARGS&&... args) {
printf("customNew2 alloc...\n");
void *buf = std::malloc(sizeof(T));
printf("customNew2 construct...\n");
auto ret = ::new(buf) T { std::forward<ARGS>(args)... };
printf("OK\n\n");
return ret;
}
template<typename T>
void customDelete2(T *ptr) {
printf("customDelete2 destruct...\n");
// what I want: a "placement delete" which calls the destructor and returns the address that should be passed to the deallocation function
// e.g.
//
// void* ptrToFree = ::delete(ptr);
// std::free(ptrToFree);
//
// equally fine would be a "magic" operator that allows one to obtain said address without actually calling the destructor:
//
// void* ptrToFree = get_deallocation_address_of(ptr);
// ptr->~T();
// std::free(ptrToFree);
ptr->~T();
printf("customDelete2 free...\n");
std::free(ptr);
printf("OK\n\n");
}
////////////////////////////////////////////////////////////////
struct A {
int a;
A() : a(0) {
printf("A()\n");
}
virtual ~A() {
printf("~A()\n");
}
};
struct B {
int b;
B() : b(0) {
printf("B()\n");
}
virtual ~B() {
printf("~B()\n");
}
};
struct C : A, B {
int c;
C() : c(0) {
printf("C()\n");
}
~C() {
printf("~C()\n");
}
};
////////////////////////////////////////////////////////////////
int main() {
C *c1 = customNew1<C>();
A *a1 = c1;
B *b1 = c1;
// Assume c and a will be the same but b is offset
printf("c: %x\n", c1);
printf("a: %x\n", a1);
printf("b: %x\n", b1);
printf("\n");
customDelete1(b1); // <- this will work, the delete expression offsets b1 before deallocing
printf("--------------\n\n");
C *c2 = customNew2<C>();
A *a2 = c2;
B *b2 = c2;
printf("c: %x\n", c2);
printf("a: %x\n", a2);
printf("b: %x\n", b2);
printf("\n");
// customDelete2(b2); // <- this will break
customDelete2(a2); // <- this will work because a2 happens to point at the same address as c2
printf("--------------\n\n");
return 0;
}
As you can see here the destructors, being virtual, are all called properly, but the deallocation of b2 will still fail because b2 points at a different address than c2.
Note that a similar problem arises when one uses placement new[] to construct an array of objects, as described here:
Global "placement" delete[]
However this can be worked around without much trouble by simply saving the array size at the head of your block of memory and handling the array constructor/destructor calls manually in a loop using single object placement new/explicit destructor calls.
On the other hand, I cannot think of any graceful way to solve the problem with multiple inheritance. The "magic" code which retrieves the original pointer from the base pointer within the delete expression is implementation specific, and there's no simple way of "doing it manually" like you can with arrays.
Here is another situation where this becomes a problem, with an ugly hack to work around it:
#include <cstdlib>
#include <cstdio>
#include <new>
////////////////////////////////////////////////////////////////
// imagine this is a library in which all allocations/deallocations must be handled by this base interface
class Alloc {
public:
virtual void* alloc(std::size_t sz) =0;
virtual void free(void *ptr) =0;
};
// here is version which uses the normal allocation functions
class NormalAlloc : public Alloc {
public:
void* alloc(std::size_t sz) override final {
return std::malloc(sz);
}
void free(void *ptr) override final {
std::free(ptr);
}
};
// imagine we have a bunch of other versions like this that use different allocation schemes/memory heaps/etc.
class SuperEfficientAlloc : public Alloc {
void* alloc(std::size_t sz) override final {
// some routine for allocating super efficient memory...
(void)sz;
return nullptr;
}
void free(void *ptr) override final {
// some routine for freeing super efficient memory...
(void)ptr;
}
};
// etc...
////////////////////////////////
// in this library we will never call new or delete, instead we will always use the below functions
// this is used instead of new...
template<typename T, typename... ARGS>
T* customNew(Alloc &alloc, ARGS&&... args) {
printf("customNew alloc...\n");
void *buf = alloc.alloc(sizeof(T));
printf("customNew construct...\n");
auto ret = ::new(buf) T { std::forward<ARGS>(args)... };
printf("OK\n\n");
return ret;
}
// um...
thread_local Alloc *stupidHack = nullptr;
// unfortunately we also have to replace the global delete in order for this hack to work
void operator delete(void *ptr) {
if (stupidHack) {
// the ptr that gets passed here is pointing at the right spot thanks to the delete expression below
// alloc has been stored in "stupidHack" since it can't be passed as an argument...
printf("customDelete free # %x...\n", ptr);
stupidHack->free(ptr);
stupidHack = nullptr;
} else {
// well fug :-D
}
}
// ...and this is used instead of delete
template<typename T>
void customDelete(Alloc &alloc, T *ptr) {
printf("customDelete destruct # %x...\n", ptr);
// set this here so we can use it in operator delete above
stupidHack = &alloc;
// this calls the destructor and offsets the pointer to the right spot to be dealloc'd
delete ptr;
printf("OK\n\n");
}
////////////////////////////////////////////////////////////////
struct A {
int a;
A() : a(0) {
printf("A()\n");
}
virtual ~A() {
printf("~A()\n");
}
};
struct B {
int b;
B() : b(0) {
printf("B()\n");
}
virtual ~B() {
printf("~B()\n");
}
};
struct C : A, B {
int c;
C() : c(0) {
printf("C()\n");
}
~C() {
printf("~C()\n");
}
};
////////////////////////////////////////////////////////////////
int main() {
NormalAlloc alloc;
C *c = customNew<C>(alloc);
A *a = c;
B *b = c;
printf("c: %x\n", c);
printf("a: %x\n", a);
printf("b: %x\n", b);
printf("\n");
// now it works
customDelete(alloc, b);
printf("--------------\n\n");
return 0;
}
This isn't a question really more of just a rant as I'm fairly sure that no magic operator or platform independent method to obtain the address exists. At the company where I work we had a library that used custom allocators with the hack above which worked okay until we had to link it statically with another program that needed to replace global new/delete. Our current solution is simply to ban the deleting of an object through a pointer to a base that can't be shown to always have the same address as the most derived object, but this seems a bit unfortunate. "ptr->~T(); free(ptr);" seems to be a common enough pattern and many people seem to think it's equivalent to a delete expression, but it's not. I'm curious if anyone else has encountered this problem and how they managed to solve it.
If p points to an object of polymorphic class type, you can get the address of the most derived object using dynamic_cast<void*>(p). Thus your customDelete2 can be implemented as follows:
template <class T>
void customDelete2(const T *ptr) {
const void* ptr_to_free = dynamic_cast<const void*>(ptr);
ptr->~T();
std::free(const_cast<void*>(ptr_to_free));
}
(Yes, you can dynamically allocate const objects.)
Since this will only compile for a polymorphic class type, you might want to remove the dynamic_cast to a helper function:
template <class T>
const void* get_complete_object_address(const T* p, std::true_type) {
return dynamic_cast<const void*>(p);
}
template <class T>
const void* get_complete_object_address(const T* p, std::false_type) {
return p;
}
template <class T>
void customDelete2(const T *ptr) {
const void* ptr_to_free = get_complete_object_address(
ptr,
std::integral_constant<bool, std::is_polymorphic<T>::value>{}
);
ptr->~T();
free(const_cast<void*>(ptr_to_free));
}
Why if I overload the -> operator in this code
class subobj
{
public:
void get()
{
printf("ea");
}
};
template<typename T> class testPT
{
public:
T* operator->()
{
return ptr;
}
T* ptr;
};
int main()
{
subobj myobj;
testPT<subobj> myclass;
myclass.ptr = &myobj;
myclass->get();
return 0;
}
I get the "ea" string printed?
By using "myclass->", that should just return a T*, a pointer to the object. I should have done something like
myclass->->get()
to actually call the get() routine. Where am I getting wrong?
operator-> is magic. :)
It uses chaining, that means it is called again as long as you don't return a plain pointer. When you return a plain pointer, it does one final call to operator->. When you call operator->
obj->foo;
it translates to:
(obj.operator->())->foo;
except when obj is a plain pointer.
You could even do this:
template<typename T> class testPT2
{
public:
T* operator->()
{
return ptr;
}
T* ptr;
};
template<typename T> class testPT
{
public:
testPT2<T> operator->()
{
testPT2<T> p2;
p2.ptr = ptr;
return p2;
}
T* ptr;
};
and it would still work by effectively applying operator-> three times.
I'm trying to make something like this work:
struct holder {
std::function<void()> destroyer;
template<typename T>
holder(T) = delete;
template<typename T>
holder(std::enable_if< WAS CREATED WITH new > pointer) {
destroyer = [=] { delete pointer; };
};
template<typename T>
holder(std::enable_if< WAS CREATED WITH new[] > array) {
destroyer = [=] { delete[] array; };
};
virtual ~holder() {
destroyer();
};
};
In a way that I could then simply make return new test; and return = new test[10]; on a function that would return holder. But I found out that it won't ever be treated as an array, as operator new[] returns a pointer.
Is there any way to achieve the desired result?
Thanks! :)
It is impossible; whether or not new or new[] was used is not part of the pointer's type information.
The only way I know of is through placement-new:
#include <new>
#include <iostream>
struct A
{
void* operator new(std::size_t n, void* ptr)
{
std::cout << "operator new()\n";
return ptr;
}
void* operator new[](std::size_t n, void* ptr)
{
std::cout << "operator new[]\n";
return ptr;
}
};
int main()
{
A* ptr;
new (ptr) A();
new (ptr) A[5];
}
For training purposes, I am trying to write my own smartpointer, imitating std::shared_ptr. I have a static std::map<void *, int> ref_track that keeps track whether there are still shared pointer referencing a certain block in memory.
My concept is this:
template <typename PType>
class shared_ptr
{
public:
shared_ptr()
: value_(nullptr), ptr_(nullptr)
{}
template <typename T>
explicit shared_ptr(T * ptr)
: shared_ptr()
{
reset(ptr);
}
template <typename T>
shared_ptr(shared_ptr<T> const & other)
: shared_ptr()
{
reset(other.get());
}
~shared_ptr()
{
reset();
}
void reset()
{
if(value_)
{
delete value_; // Segmentation fault here!
value_ = 0;
ptr_ = 0;
}
}
template <typename T>
void reset(T * ptr)
{
reset();
if(ptr)
{
value_ = new shared_ptr_internal::storage_impl<
T
>(ptr);
ptr_ = ptr;
}
}
PType * get() const
{
return ptr_;
}
typename shared_ptr_internal::ptr_trait<PType>::type operator *()
{
return *ptr_;
}
private:
shared_ptr_internal::storage_base * value_;
PType * ptr_;
};
When running my test suite, I noticed that
shared_ptr<int> a(new int(42));
a.reset(new int(13));
works fine, but
shared_ptr<int> a(new int(42));
a = shared_ptr<int>(new int(13));
leads to problems: *a is 0 instead of 13, and delete value_ crashes with a segmentation fault in the destructor of a. I have marked the crash in the source code with a comment.
The used internal classes are
namespace shared_ptr_internal
{
typedef std::map<void *, int> ref_tracker;
typedef std::map<void *, int>::iterator ref_tracker_iterator;
typedef std::pair<void *, int> ref_tracker_entry;
static ref_tracker ref_track;
struct storage_base
{
virtual ~storage_base() {}
};
template <typename PType>
struct storage_impl : storage_base
{
storage_impl(PType * ptr)
: ptr_(ptr)
{
ref_tracker_iterator pos = ref_track.find(ptr);
if(pos == ref_track.end())
{
ref_track.insert(
ref_tracker_entry(ptr, 1)
);
}
else
{
++pos->second;
}
}
~storage_impl()
{
ref_tracker_iterator pos = ref_track.find(ptr_);
if(pos->second == 1)
{
ref_track.erase(pos);
delete ptr_;
}
else
{
--pos->second;
}
}
private:
PType * ptr_;
};
template <typename PType>
struct ptr_trait
{
typedef PType & type;
};
template <>
struct ptr_trait<void>
{
typedef void type;
};
}
Sorry for the bulk of source code, but I really do not know how I could narrow it down further. I would be grateful for any ideas what could be causing the segfault, and moreover why this does not happen when using reset manually.
Update
My (not-working) assignment operator:
template <typename T>
shared_ptr<PType> & operator =(shared_ptr<T> const & other)
{
if(this != &other)
{
value_ = nullptr;
ptr_ = nullptr;
reset(other.get());
}
return *this;
}
You're missing an assignment operator.
This means that in the following code:
a = shared_ptr<int>(new int(13));
a temporary shared pointer is created; then the default assignment operator simply copies the pointer to a without releasing the old value or updating the reference count; then the temporary deletes the value, leaving a with a dangling pointer.
Seems like a violation of the rule of three: You have a custom copy constructor and a custom destructor, but no custom assignment operator. Therefore a = shared_ptr<int>(new int(13)) will just copy the two pointers value_ and ptr_ from the temporary, without any update of your reference tracking. Therefore when you destroy the temporary, there are no more tracked references to that pointer, which will lead to its deletion. Also note that the old pointer will have been leaked in the assignment.
you forgot to add an assignment operator to your pointer class that should decrement the number references to the old object and increment the number of references to the assigned object. Most times it's the easiest way to implement operator= in terms of a copy d'tor and a swap function:
shared_ptr& shared_ptr<T>::operator=( shared_ptr<T> other )
{
other.swap( this );
return *this;
}