0
#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.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
rranjik
  • 690
  • 9
  • 21
  • 4
    no, you need to allocate the memory that unique_ptr points to `std::unique_ptr ConcretePtr(new Concrete)` you are experiencing undefined behavior. – AndersK Dec 22 '17 at 07:46
  • 2
    No object of `Concrete` is created in your first example. You can firstly learn about [undefined behavior](https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior). In short, undefined behavior can do anything, thus **may** have the expected output in your first example. – xskxzr Dec 22 '17 at 07:50
  • @Praetorian the first `ConcretePtr->Bar();` works correctly. It prints "Concrete" to the console. what was `this` here ? how could `ConcretePtr->Bar();` work if `this` was nullptr ? – rranjik Dec 22 '17 at 07:50
  • 1
    Pointers are separate to what they point at... creating a pointer doesn't create anything to point at – M.M Dec 22 '17 at 07:53
  • 3
    Nothing in `Bar()` requires dereferencing `this`, the compiler probably inlines its contents and never dereferences the nullptr. It's undefined behavior even though it doesn't crash. – Praetorian Dec 22 '17 at 07:53
  • 1
    @user3222 "Appears to work" is a possible (and very devious) form of Undefined Behaviour. – Angew is no longer proud of SO Dec 22 '17 at 08:27

2 Answers2

5

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.
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Thanks. Since I did not call for allocation myself with `new` `make_unique` or `make_shared`, I think that I should be experiencing undefined behavior as others pointed out. – rranjik Dec 22 '17 at 08:08
3
    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.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • Thanks Useless. It was very helpful. Where should I learn about such implementations of `Concrete::Bar()` and `ConcretePtr->Bar()` ? But, as you mentioned I could have checked with a squawking default constructor at least. Thanks again! – rranjik Dec 22 '17 at 09:48
  • 2
    I'd advise focusing on learning how to use the things that _are_ defined first, they're also generally well-documented. Knowing why your undefined behaviour did a particular thing is probably less useful than knowing that it _is_ undefined, and how to avoid it. – Useless Dec 22 '17 at 10:38