4

foo.h

#include <iostream>
#include <memory>

class Bar
{
public:
    Bar() {};
    ~Bar() {};
    void print() {
        std::cout << "hello";
    }
};

class Foo
{
public:
    Foo(); 
    ~Foo();
    void use() {
        pteste->print();
    }
private:
    std::unique_ptr<Bar> pteste;
};

#endif

main.cpp

#include <memory>
#include "foo.h"

int main(int argc, char *argv[]) 
{
    Foo s;
    s.use();

    return 0;
}

Why and how does it works "normally"?

Thanks

EDIT: I understand about the incomplete types, but what happens when I can use unique_ptr without using new and why works

EDIT2: Organized the code better for my question

  • To start with, you should mark the destructor as `default` in the class definition. – Some programmer dude Mar 06 '14 at 07:03
  • `std::unique_ptr` is much like any other pointer, it doesn't need the complete definition when defining a variable, it just needs to know that the type exists. – Some programmer dude Mar 06 '14 at 07:05
  • @Joachim foo.cpp already has default destructor –  Mar 06 '14 at 07:05
  • What I mean is that no one else using the `Foo` class will know that the destructor has been marked with `default`. It's only known in the `foo.cpp` translation unit. – Some programmer dude Mar 06 '14 at 07:07
  • @JoachimPileborg That would break things, no? If you do that, the unique_ptr would not be able to find the `Bar` destructor. The only way for this to work is for the destructor *not* to be defined in the class definition. – juanchopanza Mar 06 '14 at 07:07
  • 1
    This is a very good related post: [Is std::unique_ptr required to know the full definition of T?](http://stackoverflow.com/questions/6012157/is-stdunique-ptrt-required-to-know-the-full-definition-of-t). – juanchopanza Mar 06 '14 at 07:10
  • @juanchopanza Are we still talking about the `default` of the **`Foo`** destructor? I fail how that will break the `Bar` destructor? – Some programmer dude Mar 06 '14 at 07:14
  • @JoachimPileborg why I need make the foo default destructor available? I only make it default, so I dont need include bar.h in the header of foo.h –  Mar 06 '14 at 07:17
  • @JoachimPileborg Sorry, my bad. I meant the `Foo` destructor. See the link above. – juanchopanza Mar 06 '14 at 07:19
  • @Tietbohl What I mean is that in `foo.cpp` you tell the compiler to use the default destructor for the class `Foo`. But that only tells the compiler to do it in the `foo.cpp` file. In `main.cpp` the compiler doesn't know that the `Foo` destructor is supposed to be the default destructor. It might cause the compiler to generate unoptimal code. – Some programmer dude Mar 06 '14 at 07:24
  • @juanchopanza good link, maybe my question is more "what happens in undefined behavior" and I only catch one particular case in my question –  Mar 06 '14 at 07:36
  • @JoachimPileborg how can I avoid includes on the header (thats why i use default constructor in implementation) or help to generate more optimal code –  Mar 06 '14 at 07:37
  • 3
    Your second edit makes the current answers pretty much useless. The current code always has the full definition of `Bar` now. – Excelcius Mar 06 '14 at 07:45
  • @Tietbohl In `main.cpp`, if the compiler knows that the destructor is the default destructor, it can easily inline the destructor. Now when compiling `main.cpp` the compiler thinks that you have a custom destructor, and will call that "custom" (but really default) destructor. – Some programmer dude Mar 06 '14 at 07:45
  • @Excelcius my question is about why it works, and how, even if I dont declare new. I think I formulated it wrong at the first time –  Mar 06 '14 at 07:47
  • @JoachimPileborg thanks! –  Mar 06 '14 at 07:56
  • @Tietbohl That's ok, but the question title and the text below the code still says there are issues with incomplete types. This is no longer the case. Please edit your question so the text and title match the current code. – Excelcius Mar 06 '14 at 08:04
  • @Excelcius yeah, maybe some moderator could deleted this question, I'll think more carefully next time, thanks –  Mar 06 '14 at 08:10
  • @Tietbohl It doesn't have to be deleted, it looks better now and it focuses on one question instead of two. – Excelcius Mar 06 '14 at 08:19

4 Answers4

3

Short answer: It doesn't work.

This reference says that the default constructor of std::unique_ptr creates an empty unique pointer, meaning it has no associated object.

The reason why this code prints hello is because this statement

std::cout << "hello";

doesn't need anything of Bar. It could just as well be a static method. Maybe the compiler inlines the function and replaces s.use() with the std::cout-statement. But even if it does call the method, you won't notice any errors since it doesn't access the memory of Bar at all.

Make a slight change to your class and you will see what I mean:

class Bar
{
public:
    Bar() : data(10) {};
    ~Bar() {};
    void print() {
        std::cout << "hello, data is: " << data;
    }

    int data;
};

Now, print accesses invalid memory, because you never called new (or even better: make_unique). It may even work and print something to the console, but the output of data will be garbage. If you're lucky, the application will crash.

Another reason why it appears to work (thanks Stas):

std::unique_ptr defines operator->, which simply returns the contained pointer, but does not check if the pointer points to valid memory. So pteste-> won't throw an exception.

Excelcius
  • 1,625
  • 13
  • 26
  • 2
    Yes, good explanation. May be it makes sense to note that `operator->()` of `unique_ptr` does nothing and just return T* as you decided to not simplify it to naked pointer ) – Stas Mar 06 '14 at 08:55
  • @Stas I added it to the answer. – Excelcius Mar 06 '14 at 09:08
  • thanks for the complete answer, I get segmentation fault trying to access data, and using only a pointer the compiler warns me about uninitialized pointer (thanks @Stas) –  Mar 06 '14 at 19:49
1

Yes, this code will "normally" print "hello" to console and it is not related to unique_ptr. You can replace std::unique_ptr<Bar> pteste with Bar* pteste in Bar and get the same result.

Consider how pteste->print() is called.

You can think about Bar::print() as a free function that take pointer to Bar object:

void print(Bar* this) {
    std::cout << "hello";
}

See, pointer passed to print(Bar*) is never touched, so you can theoretically pass whatever you want (null, garbage etc.) and it will print "hello" to console.

Stas
  • 10,615
  • 6
  • 36
  • 55
  • thanks by pointer/unique_ptr and for the implicity this, didnt know. –  Mar 06 '14 at 19:34
0

It works because

std::unique_ptr<Bar> pteste;

is a pointer declaration to the instance, it does not instantiate the pointer so it does not need to know at this point the details about Bar (e.g. ctor).

In the case of

Bar pteste

in order for pteste to be constructed it will need the know definition but since Bar is only forward declared it will give an error.

AndersK
  • 33,910
  • 6
  • 56
  • 81
0

All pointers are implemented the same way. Even though you have pointers to different types, all are the size of an int usually. So the compiler does not need to know about the type of the pointee when it compiles your code. Now if you were to dereference that pointer that would be a different story. Even if you would initialize your unique_ptr it would need to know the type, since new needs to see the constructor.

rozina
  • 3,714
  • 21
  • 43
  • thanks, yes, I notice about the constructor and destructor right now, this is because the undefined behavior I caused (I believe this is all about I wanted to know)? –  Mar 06 '14 at 07:42
  • I must admit I didn't know it was undefined behaviour. Although I have had trouble with code like yours. Cool to know it :) – rozina Mar 06 '14 at 07:47