Home > front end >  Is it possible to avoid copying a temporary class instance passed to a function that saves a pointer
Is it possible to avoid copying a temporary class instance passed to a function that saves a pointer

Time:04-15

I have a class Holder with a vector that holds instances of different classes (e.g. Derived) derived from the same abstract base class (Base). I constructed it as a vector of pointers to the base class to support polymorphism. New instances are added to the vector via a method called add. The new classes are constructed inside the parentheses when calling add and are not named (this mechanism is supplied to me and cannot be changed).

I do not quite understand what the lifetime of this constructed class is and how can I prevent it from being destructed right after exiting add. It has to be constructed, so is there a way to keep it alive and hold a valid pointer to it? Or will I have to create a copy and let the passed temporary die?

I am using C 17, none of my classes allocate memory manually, only STL containers and primitive data types are used. I've been looking at std::move() but I did not find what I needed. Example code:

class Base
{
public:
  std::vector<int> vec;
  virtual void foo() = 0;
}
class Derived : public Base
{
public:
  Derived(int a, int b)
  {
    vec.push_back(a);
    vec.push_back(b);
  }
  void foo() override;
}
class Holder
{
public:
  std::vector<Base*> ptrs;
  void add(Base& arg)     // What should be the type of arg? (Does not have to be Base&)
  {
    ptrs.push_back(&arg); // How should I add a pointer to arg into ptrs so that
                          // arg stays alive after returning from this method?
  }
}
int main()
{ 
  Holder h;
  h.add(Derived(1, 2));   // this is how add() gets called, I cannot change it
  ...
}

EDIT: To clarify, I can change the signature and body of add as well as the data type that vector ptrs uses. I cannot change the way add is called (h.add(Derived(1, 2));).

CodePudding user response:

In main

the parameter passed is a temporary destroyed just after the call to add returns. You may store this value, the address, but you must never dereference it.

If you want to ensure the the object remains valid for the lifetime of Holder your only option is taking over the ownership of an existing object or making a copy you've got ownership of.

Taking ownership of an existing object

For this using std::unique_ptr would be preferrable to make the ownership clear.

class Holder
{
    ...
    void add(std::unique_ptr<Base>&& arg)
    {
        //preferrably make ptrs a std::vector<std::unique_ptr<Base>> and use ptrs.emplace_back(std::move(arg)) or
        ptrs.push_back(args.get());
        args.release(); // make sure the object gets deleted before or during destruction of the Holder object
    }
};

Making a copy

Use the move constructor here to void a copy

class Holder
{
    ...

    template<class T>
    void add(T&& arg)     // What should be the type of arg?
    {
        ptrs.push_back(new T(std::forward<T>(arg));
        // Note: make sure the object created here gets destroyed
    }
};

EDIT

According to the updated answer this is the implementation of Holder I'd go with:

class Holder
{
public:
    std::vector<std::unique_ptr<Base>> ptrs; // make this private?

    template<class T>
    void add(T&& arg)
    {
        // better compiler error on incorrect use
        static_assert(std::is_base_of_v<Base, T>,
                      "parameter passed must be an object of a class derived from Base");

        ptrs.reserve(ptrs.size()   1); // if an std::bad_alloc happens, this gives you a strong exception guarantee
        
        ptrs.emplace_back(std::make_unique<T>(std::forward<T>(args)));
    }
};

Note: This only works with a virtual destructor of Base; if this isn't an option, you'll need to save logic for invoking the correct destructor alongside the object. You do not prevent the creation of the temporary Derived object, but at least it's possible to use the implicitly created move constructor of Derived to prevent unnecessary copies of the elements of Base::vec

  • Related