user13742796
user13742796

Reputation: 41

Create unique pointer with make_unique and move?

I have an Entity class with a private constructor and public CreateEntity function. CreateEntity stack-allocates new entities. In my app, I'd like to use a unique pointer to the returned entity. I found a solution with a temporary unique pointer and two move instructions. How do I write the two lines in Start() with make_unique and a single move instruction?

class Entity
{
public:
    Entity(const Entity&) = delete;
    Entity& operator=(const Entity&) = delete;
    Entity(Entity&&) = default;
    Entity& operator=(Entity&&) = default;
    static Entity CreateEntity();
private:
    Entity(id_t id): m_ID(id) {}
private:
    uint32_t m_ID;
};
Entity Entity::CreateEntity()
{
    static id_t currentID = 0;

    return Entity{currentID++};
}

struct Lucre : public Application
{
    bool Start();
    std::unique_ptr<Entity> m_Object;
}
bool Lucre::Start()
{
    std::unique_ptr<Entity> ptr( new Entity(std::move(Entity::CreateEntity())));
    m_Object = std::move(ptr);
}

Upvotes: 1

Views: 2546

Answers (3)

rturrado
rturrado

Reputation: 8074

I may not have understood your needs correctly, but the sample code below:

  • Also implements an Entity object in such a way that every instance has a different ID.
  • Still hides the Entity class implementation details. Before, you were creating an Entity object with auto e = Entity::CreateInstance();. Now, you can do the same with auto e = Entity{};. That is, user knows nothing about IDs in both cases.
  • Simplifies the creation of heap instances of Entity by adding a default constructor to Entity. Now, you can just initialize your Lucre::m_Object member with m_Object{ std::make_unique<Entity>() }.

[Demo]

#include <cstdint>  // uint32_t
#include <iostream>  // cout
#include <memory>  // make_unique, unique_ptr

class Entity
{
public:
    Entity(): m_ID{currentID++} {}
    Entity(const Entity&) = delete;
    Entity& operator=(const Entity&) = delete;
    Entity(Entity&&) = default;
    Entity& operator=(Entity&&) = default;

    auto getID() const { return m_ID; }
    friend std::ostream& operator<<(std::ostream& os, const Entity& e) { return os << e.m_ID; }
private:
    static inline uint32_t currentID;
    uint32_t m_ID;
};

struct Lucre
{
    Lucre() : m_Object{ std::make_unique<Entity>() } {}

    friend std::ostream& operator<<(std::ostream& os, const Lucre& l) { return os << l.m_Object->getID(); }
private:
    std::unique_ptr<Entity> m_Object{};
};

int main()
{
    Lucre lucre1{};
    Lucre lucre2{};
    std::cout << "lucre1 = " << lucre1 << ", lucre2 = " << lucre2 << "\n";
}

// Outputs:
//
//   lucre1 = 0, lucre2 = 1

Upvotes: 1

user17732522
user17732522

Reputation: 76824

Just replicating the behavior of the shown lines:

m_CameraObject = std::make_unique<Entity>(Entity::CreateEntity());

This will perform a single move construction of Entity and a single move assignment of std::unique_ptr (assuming m_CameraObject is of type std::unqiue_ptr<Entity>) in addition to the non-move/copy object constructions for Entity and std::unqiue_ptr (guaranteed in C++17 or later, otherwise likely).

The std::move are not required in this, since both Entity::CreateEntity() and std::make_unique<Entity>(Entity::CreateEntity()) are already prvalues. In your original the std::move around Entity::CreateEntity() is also redundant.

However it is difficult to tell without full code whether this is what you really want to do. It may be that letting the factory function return a std::unique_ptr<Entity> directly is the better option. Then a move constructor call for Entity wouldn't be required.

Upvotes: 2

Eugene
Eugene

Reputation: 7688

You can combine the 2 lines in Lucre::Start() like this:

m_CameraObject.reset( new Entity( Entity::CreateEntity() ) );

Here, the return of Entity::CreateEntity() is moved, but no std::move() is needed as the return is already prvalue.

Upvotes: 2

Related Questions