Why auto_ptr initialization using the assignment syntax is not allowed - c++

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

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 is std::unique_ptr reset not the same as assignment?

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{} };

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.

Can a copy-constructor take a non-const parameter?

I have this problem, there is a function foo() as follows,
vector<ClassA> vec;
void foo()
{
ClassA a; //inside foo, a ClassA object will be created
a._ptr = new char[10];
vec.push_back(a); //and this newly created ClassA object should be put into vec for later use
}
And AFAIK, vec will invoke ClassA's copy-ctor to make a copy of the newly created object a, and here is the problem. If I define ClassA's copy-ctor the usual way,
ClassA::ClassA(const ClassA &ra) : _ptr(0)
{
_ptr = ra._ptr;
}
then object a and its copy (created by vec) will have pointers _ptr pointing to the same area, when foo finishes, a will call the destructor to release _ptr, then a's copy in vec will be a dangling pointer, right? Due to this problem, I want to implement ClassA's copy-ctor this way,
ClassA::ClassA(ClassA &ra) : _ptr(0) //take non-const reference as parameter
{
std::swap(_ptr, a._ptr);
}
Is my implementation ok? Or any other way can help accomplish the job?
To answer your titular question: Yes, any constructor for a class T that has one mandatory argument of type T & or T const & (it may also have further, defaulted arguments) is a copy constructor. In C++11, there's also a move constructor which requires one argument of type T &&.
Having a non-constant copy constructor that actually mutates the argument gives your class very unusual semantics (usually "transfer semantics") and should be extensively documented; it also prevents you from copying something constant (obviously). The old std::auto_ptr<T> does exactly that.
If at all possible, the new C++11-style mutable rvalue references and move constructors provide a far better solution for the problem of "moving" resources around when they're no longer needed in the original object. This is because an rvalue reference is a reference to a mutable object, but it can only bind to "safe" expressions such as temporaries or things that you have explicitly cast (via std::move) and thus marked as disposable.
C++11 introduced move constructors for this exact purpose:
ClassA::ClassA(ClassA&& ra)
: _ptr(ra._ptr)
{
ra._ptr = nullptr;
}
Alternatively you can declare _ptr to be a shared pointer:
std::shared_ptr<char[]> _ptr;
and then default denerated copy constructor will do just fine.
You should not copy the pointer, you should copy the context that the pointer is pointing to. You should also initialize the class by telling it how many elements you want, instead of allocating it by accessing a public pointer.
Since you want to copy the object, not move it, you should allocate resources in the new object when copying.
class A {
int* p_;
int size_;
public:
A(int size)
: p_(new int[size]()),
size_(size) {
}
A(const A &a)
: p_(new int[a.size_]),
size_(a.size_) {
std::copy(a.p_, a.p_ + a.size_, p_);
}
...
};
int main () {
A a(10);
vec.push_back(a);
}
However, if you know that the object you will copy isn't used after it's copied, you could move it's resources instead.
The problem with your implementation is that you will not be able to pass temporary objects as arguments for this copy-ctor (temporaries are always const). Like already mentioned the best solution would be to move to c++11 and use move semantics. If it is not possible shared_array can be an alternative.
Additional comment:
Avoid these kind of problems creating the object with new and storing pointers to the object in the vector.

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.