std::shared_ptr or std::unique_ptr assignment operator overloads - c++

I don't see a reason why these don't have an assignment operator overload for plain old pointers of the type they're templated to. If the goal of making smart pointers interface as close to plain old pointers as they could, then why didn't they make an overload for the assignment operator like this?
inline std::shared_ptr<type> &operator=( const type * pointer)
{
reset(a);
}
this way you could use them just like you would a normal pointer, like so:
std::shared_ptr<int> test = new int;
it's not an issue at all, just wondering why they went to the trouble of just overloading a couple of operators.
Also wondering if there's a way to overload the global assignment operator to do this, or if there's any reason i shouldn't.
edit: adding a response to Nawaz about his answer here for code formatting. I just wrote this test program to see if what you were saying was right:
template<class T>
class peh
{
public:
peh() {meh = 3;}
const peh<T> & operator=(const int * peh)
{
}
};
void f( peh<int> teh)
{
}
int main()
{
int * meh = new int;
f(meh);
system("PAUSE");
return 0;
}
this here errors out saying there is no usable conversion from peh<int> to int *. so why is it acceptable with std::shared_ptr<int> to int *?

Also wondering if there's a way to overload the global assignment operator to do this, or if there's any reason i shouldn't.
No. Assignment operator overload must a member function.
By the way, if you want the following functionality, then you should not talk about assignment operator, you should rather ask : why the constructor which takes raw pointer as argument is made explicit? why it is not implicit?
//this code requires an implicit constructor, not assignment!
std::shared_ptr<int> test = new int; //illegal
It is illegal, but suppose for a while that this was allowed, then you would be able to call the following function passing a raw pointer as argument : such a feature would be dangerous, as the rest of the answer explains (read the comments) :
void f(std::shared_ptr<int> test)
{
//code
} //test will be destructed here (when it goes out of scope)
//if test.use_count() == 1, then the pointer which it manages
//will be destructed as well. (NOTE THIS POINT)
Now see the dangerous part:
int *ptr = new int;
f(ptr);
//note that calling f is allowed if it is allowed:
//std::shared_ptr<int> test = new int;
//it is as if ptr is assigned to the parameter:
//std::shared_ptr<int> test = ptr;
//Question : now what happened in f()?
//Answer : inside f(), test (the shared_ptr) will infer that no one else
//refers to the pointer it contains, because test.use_count() == 1
//test is obviously wrong in this case, because it cannot prove that!
//DANGER
*ptr = 10; //undefined behavior, because ptr is deleted by the shared_ptr
Please read the comments. It explains each part of the code snippet above.

The operator= you show would not actually enabled the syntax you want. shared_ptr<int> p = new int; would use shared_ptr's constructor from T* and shared_ptr's copy constructor. shared_ptr has both of these, but your syntax does not work because the constructor from T* is explicit.
The reason for this is because if that construction, std::shared_ptr<int> test = new int;, could be done implicitly it would mean that a shared_ptr could take ownership of a pointer without anyone ever explicitly asking it to. Nawaz shows one reason this would be really error prone; you'd have to be really careful that a pointer you're using isn't suddenly adopted by a shared_ptr somewhere without your knowledge, and then destroyed out from under you.
Here's an example that shows this dangerous implicit construction:
#include <iostream>
template<typename T>
struct owning_pointer {
T *t;
owning_pointer(T *t) : t{t} {}
~owning_pointer() {
std::cout << t << " deleted\n";
delete t;
}
};
void foo(owning_pointer<int> thief) {}
int main() {
int *i = new int;
std::cout << i << " allocated\n";
foo(i);
}
The output will be something like:
0x10d400880 allocated
0x10d400880 deleted
And see the error you get when you add explicit to owning_ptr's constructor. I get:
main.cpp:18:5: error: no matching function for call to 'foo'
foo(i);
^~~
main.cpp:13:6: note: candidate function not viable: no known conversion from 'int *' to 'owning_pointer<int>' for 1st argument;
void foo(owning_pointer<int> thief) {}
^
Also it's unnecessary to allow implicit construction from T* since there are already some perfectly simple ways to allocate without the same potential for errors:
std::shared_ptr<int> test(new int); // one extra character isn't a hardship. I typically prefer () construction anyway.
std::shared_ptr<int> test{new int}; // although I might start preferring {} construction in C++11
auto test = std::make_shared<int>(); // this is slightly different in that the allocated int is zero-initialized
If you're initializing a member shared_ptr then you can initialize it in the initializer list instead of using assignment or reset() in the body of the constructor:
struct foo {
std::shared_ptr<int> m_meh;
foo()
: m_meh(new int)
{
// no need for m_meh.reset(new int) here
}
};
What operator= would enable is this:
shared_ptr<int> s;
s = new int;
This doesn't seem quite as error prone as implicit construction of shared_ptr from T*, but I can't see that there's really any value to it either.

Related

Why auto_ptr initialization using the assignment syntax is not allowed

I was reading through this book
C++ standard library book
And here is the part i can not understand:
Note that class auto_ptr<> does not allow you to initialize an object with an ordinary pointer by
using the assignment syntax.
std::auto_ptr<ClassA> ptr1(new ClassA); //ok
std::auto_ptr<ClassA> ptr2 = new ClassA; //error
I don't understand why it is not allowed. What kind of pitfalls they were trying to avoid by not allowing initialization with assignment syntax
The fact that the assignment syntax cannot be used to initialize an auto_ptr from a raw pointer is a side effect of the constructor which takes a raw pointer being marked explicit. And the usual reason to mark a constructor as explicit is to prevent things like this:
void take_ownership(std::auto_ptr<ClassA> ptr) {
// the pointer is deleted when this function ends
}
void foo() {
ClassA obj;
take_ownership(&obj); // oops, delete will be called on a pointer to
// an object which was not allocated with new
}
The call to the take_ownership function is an error there, because of the explicit classifier on the std::auto_ptr constructor. Instead, you have to deliberately construct an auto_ptr and pass that to the function.
void foo() {
std::auto_ptr<ClassA> ptr(new ClassA);
take_ownership(ptr); // okay
}
Of course this is not completely impervious to abuse (you can still pass a non-newed object to the constructor of auto_ptr), it is at least easier to spot when an abuse is taking place.
By the way, std::auto_ptr is deprecated. It is a very broken class (due to limitations in the language at the time it was introduced). Use std::unique_ptr instead.
Here is how std::auto_ptr defined:
template< class T > class auto_ptr;
template<> class auto_ptr<void>;
Hence auto_ptr is a class type. Let's see its constructors:
explicit auto_ptr( X* p = 0 );
auto_ptr( auto_ptr& r );
template< class Y >
auto_ptr( auto_ptr<Y>& r );
template< class Y >
auto_ptr( auto_ptr_ref<Y> m );
Consider the first constructor. we can use a pointer to X type object as parameter to call this constructor:
std::auto_ptr<X> ptr1(new X); //ok
In the meanwhile, this first constructor is explicit, hence we cannot use a pointer to X type object implicitly to transform to auto_ptr<X>. In other words, we cannot initialize directly it via a pointer to X type object.
std::auto_ptr<X> ptr1 = new X; //error; cannot implicitly transform
I don't understand why it is not allowed.
At first direct initialization and copy initialization are not the same thing.
std::auto_ptr<ClassA> ptr1(new ClassA); //ok
This is direct initialization.
std::auto_ptr<ClassA> ptr2 = new ClassA; //error
This is copy initialization.
Copy-initialization is less permissive than direct-initialization: explicit constructors are not converting constructors and are not considered for copy-initialization.
So if you want to initialize std::auto_ptr with raw pointer via copy initialization, converting constructor will be needed, but std::auto_ptr doesn't have it.
std::auto_ptr's constructor taking one raw pointer as parameter is explicit, implicit conversion is prohibited.
What kind of pitfalls they were trying to avoid by not allowing initialization with assignment syntax
Consider about the following code if implicit conversion is allowed:
void f1(ClassA* p) { ... }
void f2(std::auto_ptr<ClassA> p) { ... }
...
ClassA* p = new ClassA;
f2(p); // call the wrong function, ownership is transfered to auto_ptr implicitly
p->something(); // UB, p has been deleted
delete p; // UB

Why doesn't shared_ptr permit direct assignment

So when using shared_ptr<Type> you can write:
shared_ptr<Type> var(new Type());
I wonder why they didn't allow a much simpler and better (imo):
shared_ptr<Type> var = new Type();
Instead to achieve such functionality you need to use .reset():
shared_ptr<Type> var;
var.reset(new Type());
I am used to OpenCV Ptr class that is a smart pointer that allows direct assignment and everything works fine
The syntax:
shared_ptr<Type> var = new Type();
Is copy initialization. This is the type of initialization used for function arguments.
If it were allowed, you could accidentally pass a plain pointer to a function taking a smart pointer. Moreover, if during maintenance, someone changed void foo(P*) to void foo(std::shared_ptr<P>) that would compile just as fine, resulting in undefined behaviour.
Since this operation is essentially taking an ownership of a plain pointer this operation has to be done explicitly. This is why the shared_ptr constructor that takes a plain pointer is made explicit - to avoid accidental implicit conversions.
The safer and more efficient alternative is:
auto var = std::make_shared<Type>();
The issue with allowing a raw pointer to be implicitly converted into a std::shared_ptr can be demonstrated with
void foo(std::shared_ptr<int> bar) { /*do something, doesn't matter what*/ }
int main()
{
int * bar = new int(10);
foo(bar);
std::cout << *bar;
}
Now if the implicit conversion worked the memory bar points to would be deleted by the shared_ptr destructor at the end of the foo(). When we go to access it in std::cout << *bar; we now have undefined behavior as we are dereferencing a deleted pointer.
In your case you create the pointer directly at the call site so it does not matter but as you can see from the example it can cause problems.
Allowing this allows you to call functions with pointer arguments directly, which is error prone because you're not necessarily aware at call site that you're creating a shared pointer from it.
void f(std::shared_ptr<int> arg);
int a;
f(&a); // bug
Even if you disregard this, you create the invisible temporary at the call site, and creating shared_ptr is quite expensive.
I wonder why they didn't allow a much simpler and better...
Your opinion will change as you become more experienced and encounter more badly written, buggy code.
shared_ptr<>, like all standard library objects is written in such as way as to make it as difficult as possible to cause undefined behaviour (i.e. hard to find bugs that waste everyone's time and destroy our will to live).
consider:
#include<memory>
struct Foo {};
void do_something(std::shared_ptr<Foo> pfoo)
{
// ... some things
}
int main()
{
auto p = std::make_shared<Foo>(/* args */);
do_something(p.get());
p.reset(); // BOOM!
}
This code cannot compile, and that's a good thing. Because if it did, the program would exhibit undefined behaviour.
This is because we'd be deleting the same Foo twice.
This program will compile, and is well-formed.
#include<memory>
struct Foo {};
void do_something(std::shared_ptr<Foo> pfoo)
{
// ... some things
}
int main()
{
auto p = std::make_shared<Foo>(/* args */);
do_something(p);
p.reset(); // OK
}
Why [doesn't] shared_ptr permit direct assignment [copy initialization]?
Because it is explicit, see here and here.
I wonder what the rationale [is] behind it? (From a comment now removed)
TL;DR, making any constructor (or cast) explicit is to prevent it from participating in implicit conversion sequences.
The requirement for the explicit is better illustrated with the shared_ptr<> is an argument for a function.
void func(std::shared_ptr<Type> arg)
{
//...
}
And called as;
Type a;
func(&a);
This would compile, and as written and is undesired and wrong; it won't behave as expected.
It gets further complicated with adding user defined (implicit) conversions (casting operators) into the mix.
struct Type {
};
struct Type2 {
operator Type*() const { return nullptr; }
};
Then the following function (if not explicit) would compile, but offers a horrible bug...
Type2 a;
func(a);

could you tell me why does this code crash?

So I'm curious of the reason the following code crashes.
Will appreciate help.
#include <iostream>
using namespace std;
class temp
{
public:
temp(int i)
{
intPtr = new int(i);
}
~temp()
{
delete intPtr;
}
private:
int* intPtr;
};
void f (temp fInput)
{
cout << "f called" << endl;
}
int main()
{
temp x = 2;
f(x);
return 0;
}
You are violating The Rule of Three
You maintain a pointer member and you pass a copy of the object to the function f. So, the end result is that you call delete twice on the same pointer.
The crash occurs because of the way you are passing x.
After the scope of the f function, x's destructure will be called and will delete intPtr.
However, that will delete memory that is still in scope for main. Therefor, after return 0 is called, it will try to delete memory that already exists as you are calling delete twice on the same pointer.
To fix this error, change
void f (temp fInput)
to
void f (const temp& fInput)
Alternatively, you could look into using a std::shared_ptr.
The pointer is copied when x is passed (implicit copy constructor) and the destructor is called twice (before the function returns and before main returns), thus the memory is deleted twice.
Use std::shared_ptr<int> here instead instead of a raw int pointer (assuming you want the behavior to be the same, i.e. reference the same int from both temps; otherwise, implement the copy constructor, move constructor and assignment operator yourself).
#include <memory>
class temp {
public:
temp(int i) : intPtr(std::make_shared<int>(i)) {
}
private:
std::shared_ptr<int> intPtr; // reference counting ftw
};
The problem you're hitting here is a double delete. Because you didn't define any copy constructors here C++ is happily doing so for you. The implementation does this by performing a shallow copy of all of the contents.
f(x);
This line works by creating a copy of x and passing it to f. At this point there are 2 temp instances which own a single intPtr member. Both instances will delete that pointer and this is likely what is causing your crash.
To work around this you can take a number of steps
Use a pointer type meant for sharing like shared_ptr<T>
Create an uncallable copy constructor which forces the instance to be passed by ref
An example of #2 is
class temp {
temp(const temp& other);
temp& operator=(const temp& other);
public:
// Same
};
Now the f(x) line simply won't compile because it can't access the necessary copy constructor. It forces it to instead redefine f to prevent a copy.
void f(const temp& fInput) {
...
}

Is there any reason why the `explicit` keyword is used in the constructor of std::auto_ptr?

This is the ctor used to construct a std::auto_ptr object from a standard pointer, in VS2008 compiler.
template<class _Ty>
class auto_ptr
{
public:
explicit auto_ptr(_Ty *_Ptr = 0) _THROW0() : _Myptr(_Ptr) {}
private:
_Ty *_Myptr;
};
Is there any particular reason why the explicit keyword is used above ?
In other words, why can't I initialize an auto_ptr with
std::auto_ptr<Class A> ptr = new Class A; ?
Becasue you could otherwise unintentionally do something like:
void foo(std::auto_ptr<int> p)
{
}
void boo()
{
int* p = new int();
foo(p);
delete p; // oops a bug, p was implicitly converted into auto_ptr and deleted in foo.... confusing
}
In contrast with where you are actually explicitly aware of what is happening:
void boo()
{
int* p = new int();
foo(std::auto_ptr<int>(p)); // aha, p will be destroyed once foo is done.
}
Constructing a std::auto_ptr is a transfer in ownership. It is best for everyone involved that transfers of ownership be kept explicit. For instance:
void frobnicate(const std::auto_ptr<Class> & ptr);
Class *instance = new Class();
frobnicate(instance);
delete instance;
If the constructor was implicit, then this code would compile, and it would be nearly impossible to notice that it was wrong without checking the definition of frobnicate. Besides, while using = for initialization is now harder, you can still use the other initialization syntax:
std::auto_ptr<Class> instance(new Class);
frobnicate(instance);
First, solving the immediate issue, you can initialize it like this:
std::auto_ptr<Class A> ptr(new A);
Second, implicit conversions can cause more harm than good, so it's a good reflex to make constructors callable with a single parameter explicit to start with and then ponder if implicitness could be valuable.
What's wrong with just using:
std::auto_ptr<T> ptr(new T());
If you allow implicit conversion then you can run into all sorts of issues with implicit casts being inserted where they are least expected.

Why doesn't auto_ptr construction work using = syntax

I ran into a compiler error that didn't make much sense to me:
#include <memory>
using namespace std;
auto_ptr<Table> table = db->query("select * from t");
error: conversion from 'Table*' to non-scalar type 'std::auto_ptr< Table>' requested
However, the following line does work:
auto_ptr<Table> table(db->query("select * from t"));
What is it about this definiton of the constructor that prevents it from working as I expect? I thought that initialized declarations used the constructors.
Here's my auto_ptr's constructor (from the SGI STL):
explicit
auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }
It's the "explicit" keyword.
template <typename T>
struct foo
{
explicit foo(T const *)
{
}
};
template <typename T>
struct bar
{
bar(T const *)
{
}
};
int main(int argc, char **argv)
{
int a;
foo<int> f = &a; // doesn't work
bar<int> b = &a; // works
}
The "explicit" keyword prevents the constructor from being used for implicit type conversions. Consider the following two function prototypes:
void baz(foo<int> const &);
void quux(bar<int> const &);
With those definitions, try calling both functions with an int pointer:
baz(&a); // fails
quux(&a); // succeeds
In the case of quux, your int pointer was implicitly converted to a bar.
EDIT: To expand on what other people commented, consider the following (rather silly) code:
void bar(std::auto_ptr<int>);
int main(int argc, char **argv)
{
bar(new int()); // probably what you want.
int a;
bar(&a); // ouch. auto_ptr would try to delete a at the end of the
// parameter's scope
int * b = new int();
bar(b);
*b = 42; // more subtle version of the above.
}
You need to use
auto_ptr<Table> table = auto_ptr<Table>(db->query("select * from t"));
auto_ptr does not define an assignment operator for it's template type. The only allowed assignment is from another auto_ptr (and it's constructor from the pointer is explicit). This is done to protect accidental misuse of auto_ptr, as auto_ptr assumes ownership of the memory.
My guess is that you need the assignment form to use multiple queries after another like:
// initialize using constructor
auto_ptr<Table> table(db->query("select * from t1"));
...
// new query using assignment
table = auto_ptr<Table>(db->query("select * from t2"));
...
// another query using assignment
table = auto_ptr<Table>(db->query("select * from t3"));
The constructor is declared as explicit, which means that it won't be used for implicit type casting. Implicit conversion to auto_ptr could easily lead to undesirable situations since the auto_ptr is taking ownership of the pointer.
For example, if auto_ptr would allow implicit conversion from a pointer and you accidentally passed a pointer to a method taking an auto_ptr the pointer would be silently converted to an auto_ptr and subsequently deleted when the function ends, even if that wasn't the intention. But by marking the constructor as explicit conversion can no longer happen silently and by calling the constructor you clearly express the intention of passing ownership to the auto_ptr, thus avoiding any potential confusion.
void fun(std::auto_ptr<Foo> foo) // Assume implicit conversion is allowed.
{
// do stuff with foo
}
Foo *foo = new Foo();
f(foo); // Normally this isn't allowed.
foo->bar(); // Oops
Adding to what lothar said: Because the auto_ptr constructor is declared with the explicit keyword, you need to use an explict cast to create an auto_ptr from a raw pointer. (Before the introduction of explicit, implicit casting was the bane of many a new -- and experienced) C++ developer.)