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