0

How would i write the following with smart ptrs:

class App
{
public:
    Project* GetProject();
    SetProject( Project* project );
private:
    Project* project;
}

Project* project = new Project();
App* app = new App();

app.SetProject( project );
Project* retrievedProject = app.GetProject();

I am mostly interested in the method paramters and return techniques... Is something like this correct?

class App
{
public:
    void App::SetProject( unique_ptr<Project> project )
    {
        this->project.swap( project );
    }

    Project* App::GetProject() const
    {
        return this->project.get();
    }
private:
    unique_ptr< Project > project;
}

...

unique_ptr< Project > project( new Project() );
unique_ptr< App > app( new App() );

app.SetProject( project );
unique_ptr< Project > retrievedProject = app.GetProject();

This is related to this question: Should i stop using * pointers? - A few entry questions

I am slightly confused at what the rules are now... Thanks in advance!

Edit for minor question:

Galik, you had mentioned that it's rare that you want to declare Project outside of the function call and then pass it into with release... I have the following (for example):

unique_ptr<Project>project( new Project() );
project.Title = "Bob";
project.Type = PROJECTYPE.DISTRIBUTION;
project.Manager = manager;

app.SetProject( project.release() );

Is this perfectly normal? My issue is that I am actually creating tools for gaming and i'm worried that the constant flagging for deletion is going to be causing micro stutters as garbage collection is kicking in... Am I just worrying for worry sake?

Community
  • 1
  • 1
Jimmyt1988
  • 20,466
  • 41
  • 133
  • 233
  • [cppreference.com](http://en.cppreference.com/w/cpp/memory/unique_ptr#Example) – Axalo May 01 '15 at 01:17
  • Does the app "own" the project? (i.e. is it responsible for deleting the project?) – user253751 May 01 '15 at 01:22
  • Not sure really, It may be that i wish to delete it somewhere else later on. At the moment, I guess my App class will do that deleting... But i'd like to keep it open ended. – Jimmyt1988 May 01 '15 at 01:24
  • I've added my guess at how to do it, I guess a yes/no would be v helpful!!! – Jimmyt1988 May 01 '15 at 01:26
  • C++14 you can use `std::make_unique` to construct an object and wrap it in a `std::unique_ptr`, instead of using `new` keyword – Andreas DM May 01 '15 at 01:42

2 Answers2

1

Here is a simple example with unique_ptr

#include <iostream>
#include <memory>

struct Project 
{
    ~Project()
    {
        std::cout << "Destructor..." << std::endl;
    }
};

class App
{
    std::unique_ptr<Project> _uptr;
public:
    // set _uptr to nullptr by default 
    App(std::unique_ptr<Project> uptr = nullptr): _uptr(std::move(uptr)){} 
    std::unique_ptr<Project> GetProject()
    {
        return std::move(_uptr);
    };
    void SetProject(std::unique_ptr<Project> project)
    {
        // need std::move, unique ownership
        _uptr = std::move(project);
    };
};

int main()
{
    std::unique_ptr<Project> project(new Project);
    std::unique_ptr<App> app(new App);
    // need std::move, unique ownership
    app->SetProject(std::move(project));
}
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • Ahhhhhh so you return a movement of a pointer to transfer ownership? And you move a project passed in, into the private _uptr to transfer ownership once again? thanks for the example, this is AWESOME – Jimmyt1988 May 01 '15 at 01:27
  • So the move keyword is something you see quite a lot of these days then? – Jimmyt1988 May 01 '15 at 01:29
  • 1
    @Jimmyt1988, yes, you need to return via `std::move`, as `unique_ptr` cannot be copied in the return type (its copy ctor is deleted). You basically need to move all the time you use a `unique_ptr` (remember that no-one else can share owenership). And yes, `std::move` is seen quite often. It doesn't move anything, it just casts to a rvalue reference, and the left hand side is invoking the proper move constructor or move assignment operator. – vsoftco May 01 '15 at 01:30
  • Is this an expensive operation. For example, might it be uncommon to use these in a game due to performance? Or is this just the way it is now? P.S thankyou for your awesome input.. This is 500% clearer to me now... It helps me absorb some of the other articles better. – Jimmyt1988 May 01 '15 at 01:30
  • 1
    @Jimmyt1988 `move` is just a cast. The move constructor or assignment operators are then using the rvalue reference to "steal" the resource. In general, a move is very much like a swap, so if properly implemented is very cheap. Those 2 questions and their answers may be helpful: http://stackoverflow.com/questions/3106110/what-are-move-semantics?lq=1 and http://stackoverflow.com/q/3413470/3093378 – vsoftco May 01 '15 at 01:32
  • Arrr, thank you thank you. This is marvelous #knowledgeispower - @vsoftco - Can i still (for sakes sakes) call reset on objects i have instantiated. This will not intefere with the natural deletion of the objects? – Jimmyt1988 May 01 '15 at 01:40
  • @Jimmyt1988 what exactly do you mean by "reset"? – vsoftco May 01 '15 at 01:47
  • unique_ptr has a method called reset... which apparently deletes the memory for that object. I wondered if for clarity and loveliness i could call reset ont he objects in my destruct. – Jimmyt1988 May 01 '15 at 01:48
  • 1
    @Jimmyt1988 once you call a reset on a `unique_ptr`, it will invoke the deleter, which will invoke the destructor of the managed object, which will hence be deleted. After that your pointer will manage another object. So there's no danger here. Even if you reset to `nullptr` in the destructor you should still be ok. Basically it's hard to shoot yourself in the foot with a smart pointer, unless you try really hard. To convince yourself, add `~App() {_uptr.reset(nullptr); }` as a destructor of `App`, and you'll see it works. – vsoftco May 01 '15 at 01:51
  • @Jimmyt1988 but of course there's no need to do this manually, that's the whole idea of the smart pointers, their destructor does it automatically at the end of scope. – vsoftco May 01 '15 at 01:56
  • Note: in this answer's code, calling `a.GetProject` makes `a._uptr` null. If you don't want that, then you should make `GetProject` return a plain `Project*` (and not use `std::move`) – user253751 May 01 '15 at 02:00
  • @immibis or maybe use a `std::shared_ptr`, again depends on what exactly one tries to model/achieve. – vsoftco May 01 '15 at 02:02
1

The answer to this really depends on what you are wanting to do. You should use smart pointers to express ownership and you should not really be passing smart pointers about if you can at all help it.

If for example your App class is supposed to take care of the various Project objects and only give access to the Project to outsiders then you should express that by returning a reference to the project.

That way several callers can access the internal Project that is owned by the App.

Rather like this:

class Project
{
public:
    void do_stuff() {}
};

// This class is the Project Owner
// Meaning it is responsible for
// managing its life-time
class App
{
public:
    // return a reference expressing the caller
    // does not gain ownership merely access
    Project& GetProject() { return *project; }

    // replace configured Project
    // accepting a std::unique_ptr expresses the fact that
    // this function takes ownership of the Project
    void SetProject(std::unique_ptr<Project> project) { this->project = std::move(project); }

private:
    std::unique_ptr<Project> project;
};

int main()
{
    App app; // don't make pointers if we don't have to

    // project has ownership of the raw pointer
    std::unique_ptr<Project> project(new Project());

    // std::move() transfers ownership to the function's parameter
    app.SetProject(std::move(project));

    // get a reference to the internal Project
    Project& retrievedProject = app.GetProject();

    // work with configured Project
    retrievedProject.do_stuff();
}
Galik
  • 47,303
  • 4
  • 80
  • 117
  • In your `int main()` - If you were to create a `unique_ptr project( new Project() )` and then pass that into `SetProject`... (which is what i'm going to need to do), then i guess i just do `SetProject( move( project ) )`... to transfer the ownership to the App. – Jimmyt1988 May 01 '15 at 01:51
  • @Jimmyt1988 Its unusual to great a `std::unique_ptr` if you immediately pass its ownership to another object like `App` but if you are in that situation (in my example) you would call `project.release()` on your smart pounter. I will add it as an example. – Galik May 01 '15 at 01:55
  • @Jimmyt1988 ok I provided an example – Galik May 01 '15 at 01:57
  • @Galik Personally I'd prefer to make `SetProject` take a `unique_ptr` - it reminds the caller (or at least the programmer writing the caller) that this function does take ownership, and helps prevent silly mistakes. – user253751 May 01 '15 at 02:01
  • Thanks immibis... So it's like a good practice thing... Love that! – Jimmyt1988 May 01 '15 at 02:02
  • @Galik - You say don't make pointers if we don't have to. My App class is most likely going to get quite hefty... I imagine taking up all the stack memory isn't a good option... This is for a win32 app and i have declared it in global scope. Do you have any preferences or opinions with this new context? – Jimmyt1988 May 01 '15 at 02:06
  • 1
    @Jimmyt1988 Sure you do whatever is good for your situation. However what a lot of libraries do is store the "hefty" data on the heap internally (using a smart pointer) so that the containing object can sit on the stack. For example a `std::vector` stores all its data on the heap through a pointer so you can put your `std::vector` on the stack without worrying how big it will grow. That's not always appropriate so do whatever is right for you. – Galik May 01 '15 at 02:16
  • Hey Galik, I had one more question and I've put it in the OP... Any ideas? – Jimmyt1988 May 01 '15 at 09:36
  • 1
    @Jimmyt1988 My original comment was that it seems wasteful to create a smart pointer and then immediately pass its raw pointer on to someone else. But that is unlikely to be a performance problem unless you do it inside a tight loop. The general advice here it to profile application and see of this function is a bottleneck. If you're worried then my original interface whereby `SetProject` receives a raw pointer would allow you to avoid creating (and then immediately destroying) the external smart pointer but, as pointed out, you sacrifice having slightly less well defined semantics. – Galik May 01 '15 at 11:34
  • 1
    @Jimmyt1988 However I see nothing wrong with accepting a raw pointer in your `SetProject()` function to avoid needlessly creating a `std::unique_ptr`. Accepting a raw pointer is a strongly implies that the function is accepting ownership after all, otherwise you would generally pass in a reference. – Galik May 01 '15 at 11:40
  • any reason why you changed from reset to std::move? – Jimmyt1988 May 02 '15 at 00:07
  • 1
    @Jimmyt1988 You expressed a desire to change the interface to accept a `std::unique_ptr` rather than a raw pointer. If you put back the raw pointer version `SetProject(Project* project);` then `reset()` would be required. – Galik May 02 '15 at 00:15
  • Ah i see, i actually gave it a try and realised they weren't compatible... Thanks man! – Jimmyt1988 May 02 '15 at 00:26
  • Returning a reference is great if you can guarantee that `project` is not null. Otherwise I would prefer to return a raw pointer and leave it as the responsibility of the caller to do the null check. I don't see any guarantees here that `project` is not null. It could never have been set or could have been explicitly set to null. – Chris Drew May 03 '15 at 01:32