0

Is it legal to destruct and construct the elements of a vector externally? Assuming that you leave the same set of elements in a constructed state that you started with, of course? And assuming to don't do anything obviously dumb, like calling the destructor twice on the same element, or similar.

Leaving exceptions aside, I'm envisioning something like the following:

int main ()
{
  std::vector<std::string> v (10, "Hello");

  v [2].std::string::~string ();

  new (&v [2]) std::string ("World");
}

Or would something like this be UB? What about the other standard containers?

levzettelin
  • 2,600
  • 19
  • 32
  • 2
    Can you explain why you want to do this? What is the benefit of doing this instead of "overwriting" the value (using normal assignment operators) – UnholySheep Mar 04 '18 at 21:08
  • You're taking a big gamble here. If you can't re-construct v[2] for some reason you'll end up calling the destructor on an object that's already been destroyed. @UnholySheep is right--just overwrite the value. – Stephen Newell Mar 04 '18 at 21:09
  • @UnholySheep I don't want to do this. I'm just curious what the answer is. – levzettelin Mar 04 '18 at 21:11
  • I think technically you might need to `std::launder` the data pointer if you do that to `v[0]`, but you can't. – nwp Mar 04 '18 at 21:12
  • @nwp Isn't that needed only when the class contains references or const member variables? `std::string` is not guaranteed not to, but it really shouldn't. – HolyBlackCat Mar 04 '18 at 21:13
  • @HolyBlackCat I think you always need to do that because technically the data pointer would be dangling because the pointee was destroyed, even though a new one was constructed in its place. I'm not enough of a language lawyer to tell. – nwp Mar 04 '18 at 21:18

2 Answers2

4

This is perfectly valid, just as you can always explicitly call the destructor of any object. However, it may be stupid. Between destroying and resurrecting the element, the vector is in a state where it can not be destroyed. If for some reason its destructor is called, you get UB. That is especially dangerous because you probably won't do these two steps one after the other.

Now, you don't just do this for fun. If you wanted e.g. to erase an element, you could create a temporary, swap that with the element and destroy the temporary then. That way, you would not have this window where you can't destroy the vector. Also, this approach works even for not-so-trivial cases like std::string element but also for more complex vector elements.

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
  • Is it true what @nwp said above, i.e., if you do it on ``v[0]`` you have to ``std::launder`` the data pointer? – levzettelin Mar 05 '18 at 10:41
  • I don't know. I honestly don't understand which (obscure corner-?)case `std::launder` was made for. I'm reading https://stackoverflow.com/questions/39382501/what-is-the-purpose-of-stdlaunder currently, maybe that sheds some light on these questions. – Ulrich Eckhardt Mar 06 '18 at 11:46
1

You could do that, but the slot in the vector would be invalid between the destruction and re-construction. If for some reason (exception perhaps), the vector's DTOR gets called before re-construction, undefined behavior ensues.

Below is a safer alternative. Any std::string implementation of reasonable quality will construct a new std::string with "World" in it, then use an rvalue-assignment operator (move-semantics) to put it into the vector slot. No cycles were harmed during the execution of this assignment.

#include <string>
#include <iostream>

int main()
{ 
    v[2] ="World";
    std::cout << v[2] << std::endl;
    return 0;
}
Jive Dadson
  • 16,680
  • 9
  • 52
  • 65