Reputation: 792
#include <iostream>
#include <memory>
class Base
{
public:
virtual void foo() = 0;
};
class Derived : public Base
{
public:
void foo() override { std::cout << "Derived" << std::endl; }
};
class Concrete
{
public:
void Bar() { std::cout << "concrete" << std::endl; }
};
int main()
{
std::unique_ptr<Concrete> ConcretePtr = nullptr;
ConcretePtr->Bar();
std::unique_ptr<Base> BasePtr;
BasePtr->foo();
return 0;
}
I assume declaring a unique_ptr to a concrete type Concrete
, allocates memory for an object of type Concrete
and the unique_ptr starts pointing to it. Is my assumption/understanding correct ? I ask because ConcretePtr->Bar();
prints "concrete" to the console. But, if I make a unique pointer to an interface Base
, it does not know the exact type of object that I need and does not allocate/acquire resources in the memory.
This fails at BasePtr->foo();
with BasePtr._Mypair._Myval2 was nullptr.
Why does the first declaration std::unique_ptr<Concrete> ConcretePtr = nullptr;
allocate an object by itself ? what if I did not want it to be pointing to some real object at that very line of code, but wanted only a smart pointer ?
Now if I change the declaration to be std::unique_ptr<Concrete> ConcretePtr;
and the Concrete
type to be the following,
class Concrete
{
int ConcreteNum;
public:
void Bar()
{
std::cout << "concrete" << std::endl;
ConcreteNum = 38;
std::cout << ConcreteNum << std::endl;
}
};
it fails at ConcreteNum = 38;
complaining that this
was nullptr
; if this this
was nullptr
then why and how did the earlier call (where Concrete
did not have any state ConcreteNum
) to Bar
work ?
Moreover why does it not fail at ConcretePtr->Bar();
(this ->
requires a concrete object, does it not ? what was this
here ?) but inside Bar
, in that assignment ?
I see the same issue with std::shared_ptr
as well. I'm not very sure of the difference between declaration, initialization & assignment. Please help me understand.
I'm using MSVC.
Upvotes: 0
Views: 978
Reputation: 67723
std::unique_ptr<Concrete> ConcretePtr = nullptr;
I assume declaring a unique_ptr to a concrete type Concrete, allocates memory for an object of type Concrete and the unique_ptr starts pointing to it. Is my assumption/understanding correct ?
Well, you can trivially check. Write a default constructor for Concrete
that prints something out so you can tell when an instance is created. Run the smallest possible program (just the line above in main
). Did you see the expected output?
You should be checking this stuff before asking a question (and probably after reading the documentation), but to save you time: no, that line doesn't construct an object of type Concrete
.
You can also check explicitly whether unique_ptr
is managing an object, with
if (!ConcretePtr) {
std::cout << "ConcretePtr doesn't point to anything\n";
} else {
std::cout << "ConcretePtr owns an object\n";
}
This check is also trivial, and you could easily do it before asking a question.
I ask because
ConcretePtr->Bar();
prints "concrete" to the console
This is a bad test because if the pointer is a nullptr
, it's undefined behaviour. If you care whether the pointer is a nullptr
, you should check that explicitly before dereferencing it, as above.
To demonstrate why this test is confusing you (and you should use the ones above in preference), consider a likely implementation of non-virtual member functions (recall they get an implicit this
pointer):
// void Concrete::Bar() implemented as
void Concrete_Bar(Concrete *this)
// and ConcretePtr->Bar() implemented as
Concrete_Bar(ConcretePtr.get());
so, you just passed a nullptr
to a function that ignores its only parameter, and you never tested the thing you thought you did.
Upvotes: 3
Reputation: 69864
The unique_ptr
models a pointer. That is, it's an object that points to another object.
Initialising the unique_ptr
with nullptr creates it in the state where it is not pointing to or owning another object.
It's like saying Concrete* p = nullptr
.
Initialise it in one of these ways:
std::unique_ptr<Concrete> p{new Concrete()};
or
std::unique_ptr<Concrete> p; // = nullptr is implied.
p.reset(new Concrete());
or, better:
std::unique_ptr<Concrete> p = std::make_unique<Concrete>();
or simply:
auto p = std::make_unique<Concrete>();
But be careful in this case if you really want to be pointing to the Base interface:
std::unique_ptr<Base> p = std::make_unique<Derived>();
or
std::unique_ptr<Base> p = nullptr;
p = std::make_unique<Derived>(); // assignment from rvalue ref of compatible unique_ptr.
Upvotes: 5