Why is std::unique_ptr reset not the same as assignment? - c++

I am trying to understand why
std::unique_ptr<MyClass> p = new MyClass;
Does not work, but
std::unique_ptr<MyClass> p;
p.reset(new MyClass);
is fine. I somewhat understand how they are different, but I would like to know why the choice was made to make them different. What is the danger in assignment not being the same as reset?

Firstly, std::unique_ptr<MyClass> p = new MyClass; is not assignment, it is copy initialization. And it doesn't work because the constructor of std::unique taking a raw pointer is marked as explicit:
explicit unique_ptr( pointer p ) noexcept;
It is declared as explicit to avoid unexpected (might be dangerous) implicit conversions, eg:
void foo(std::unique_ptr<int> uptr);
int *rptr = new int;
foo(rptr); // suppose rptr is implicitly converted to std::unique_ptr<int>
// then the ownership is passed to the parameter uptr
// when foo() returns uptr is destroyed; the pointer managed by it is deleted too
// since rptr has been deleted continue to deference on it leads to UB
*rptr = 42; // UB
Note that explicit constructors are not considered in copy initialization (eg std::unique_ptr<MyClass> p = new MyClass;). You can use them in direct initialization instead (eg std::unique_ptr<MyClass> p (new MyClass);). They are used to prohibit implicit conversions, but you can perform explicit conversions. Like the usage of reset, you have to do these things explicitly, to show (and make yourself) that you're pretty sure about what you're doing.
BTW: The assignment from raw pointer doesn't work either, because std::unique_ptr doesn't have an overloaded assignment operator taking a raw pointer as parameter. For the reason above, raw pointer can't be implicitly converted to std::unique_ptr, so the move assignment operator (which takes std::unique_ptr as parameter) won't be considered either.

I am trying to understand why
std::unique_ptr<MyClass> p = new MyClass;
does not work
The same reason as #songyuanyao mentioned, where it's declared explicit, tells that you can still initialize it in a different form of initialization that surpasses explicit:
// Valid, since now it's 'explicit'
std::unique_ptr<MyClass> p { new MyClass{} };

Related

Why std::move don't change source variable to default value in default move constructor?

I try to understand the move constructor.
I allocate memory in the class' constructor and destroy it in the destructor.
When I try to move the class, I still have a double free.
#include <algorithm>
class TestClass
{
public:
TestClass() {a_ = new int[1];}
TestClass(TestClass const& other) = delete;
TestClass(TestClass && other) noexcept // = default;
{
this->a_ = std::move(other.a_);
}
~TestClass() {delete[] a_;}
private:
int* a_ = nullptr;
};
int main( int argc, char** argv )
{
TestClass t;
TestClass t2 = std::move(t);
}
Why std::move do not change to nullptr other.a_ ?
I have the same problem if the move constructor is default.
I found the following questions but I still don't know why the move operator don't change the source variable to default value.
How does std::move invalidates the value of original variable?
C++ how to move object to a nullptr
C++ std::move a pointer
std::move just produces an rvalue (xvalue); it won't perform move operation, it won't modify the argument at all.
In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type.
Given this->a_ = std::move(other.a_);, as built-in type, i.e. int*, this->a_ is just copy-assigned from ohter.a_, then both the pointers point to the same object. The defaulted move constructor does the same thing in fact. (It performs member-wise move operation on the data members; note that for built-in types the effect of move is same as copy.)
You need to set other.a_ to nullptr explicitly if you want to define that after moved the object should contain a null pointer.
E.g.
TestClass(TestClass && other) noexcept
{
this->a_ = other.a_;
other.a_ = nullptr;
}
First, std::move is just a cast which leads to other.a_ to be treated as an rvalue. For pointers, a move is just a copy.
This is so, I presume, because clearing the source pointer is not necessary in all cases and it would cause overhead in the cases where it's not needed.
You need to do the clearing explicitly.
Or, even simpler, just use std::unique_ptr<int> a_. Then you don't need to define any special member functions and the class behaves as you would imagine.

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

Do pointers have copy constructors?

I am transiting from C pointers to C++ ones,now learning about auto_ptr. Here is the program I tried:
#include <iostream>
#include <memory>
#include "Car.h"
using namespace std;
typedef auto_ptr<Car> CarPtr;
int main() {
CarPtr au_ptr1(new Car());
CarPtr au_ptr2 = new Car();
Car *norm_ptr1 = new Car();
Car *norm_ptr2(new Car());
int *i_ptr1=new int();
int *i_ptr2(new int());
}
Statements like the following mean what?
int *i_ptr2(new int());
Car *norm_ptr2(new Car());
The statement mentioned above compiled successfully.And the following one,throws a compilation error - CarPtr au_ptr2 = new Car();
Why is that?
Thanks in Advance
It does have a copy constructor, but the conversion constructor is explicit, which is what is causing the error:
explicit auto_ptr (X* p=0) throw();
Which means a Car* can't be implicitly converted to a auto_ptr<Car>, which is what
CarPtr au_ptr2 = new Car();
attempts to do. This is called copy-initialization, as opposed to:
CarPtr au_ptr1 (new Car());
which is value-initialization. The first version will attempt to create a temporary CarPtr from a Car* and use it to initialize au_ptr2. The second one calls the copy constructor directly.
Statements like
int *i_ptr2(new int());
simply value-initialize the pointer with the value in the parenthesis.
Typically objects have copy constructor and pointers are not objects so they don't have copy constructor (or assignment operator or destructor). More precisely, pointers rely on the default copying mechanism.
When you talk about auto_ptr or any other smart pointers, they are just pointer namesakes. But in actual they are templatized objects which use the RAII mechanism.
CarPtr au_ptr2 = new Car(); // this is initialization not assignment
gives compilation error because corresponding CarPtr::CarPtr(...) constructor is made explicit, so it doesn't accept the = style initialization.
Raw pointers don't have constructors, but for most purposes they can be used as if they do. The built-in types can all be initialized from a value of any type convertible to their type, just like a user-defined class with a copy constructor can.
int *i_ptr1=new int();
int *i_ptr2(new int());
mean the same thing.
I think the reason for this is basically templates: it means that you can use a type T as though it were a user-defined type, and write T t(0); or T(0) or T(), and when T happens to be a built-in type the meaning is exactly the same as T t = 0; or (T)0 or (T)0 (again). Actually, the meaning of T(0) is by definition the same as (T)0 regardless of what constructors T has, but people who tell you not to use C-style casts in C++ code try to ignore that fact ;-)
auto_ptr does in fact have a copy constructor, but unlike most copy ctors it takes a non-const parameter, and modifies its argument. That's why in C++11 it's deprecated in favor of unique_ptr, which doesn't have a copy constructor but does have a move constructor.
As Luchian says, the problem with CarPtr au_ptr2 = new Car(); isn't (just) the copy constructor, it's also the lack of an implicit conversion from the type of new Car();, Car*, to auto_ptr<Car>. Copy initialization tries to implicitly convert the RHS to the type of the LHS, and then copy it to the LHS. Both of those fail in this example. Direct initialization is allowed to use explicit conversions and doesn't need a copy, so it succeeds.
One way in which built-in types don't behave as if they have constructors is default initialization. You can write:
int i = int();
and i is guaranteed initialized to zero. So you might imagine that it has a no-args constructor that sets it to zero. But if int were really a class type with that constructor, then writing:
int i;
would also guarantee i is zero, and it doesn't (at least, not in function scope).
By the way, don't get too excited by all this and accidentally invoke the so-called "most vexing parse".
int i();
is equivalent to int i(void);, not int i(0);. It declares a function, not an integer variable.

std::shared_ptr or std::unique_ptr assignment operator overloads

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.

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.