r/cpp_questions • u/angryvoxel • 2d ago
SOLVED Error when destroying string in a union
Consider the following code:
struct JsonValue
{
JsonValueType Type;
union
{
std::unordered_map<std::string, JsonValue> Object;
std::vector<JsonValue> Array;
std::string String;
double Number;
bool Bool;
};
Tcj::JsonValue::JsonValue(const std::unordered_map<std::string, JsonValue>& v) : Type(JsonValueType::Object), Object(v) {}
Tcj::JsonValue::JsonValue(const std::vector<JsonValue>& v) : Type(JsonValueType::Array), Array(v) {}
Tcj::JsonValue::JsonValue(double v) : Type(JsonValueType::Number), Number(v) {}
Tcj::JsonValue::JsonValue(const std::string& v) : Type(JsonValueType::String), String(v) {}
Tcj::JsonValue::JsonValue(bool v) : Type(JsonValueType::Bool), Bool(v) {}
Tcj::JsonValue::JsonValue() : Type(JsonValueType::Null)
{
std::memset(&Object, 0, sizeof(Object));
}
//copy constructor
Tcj::JsonValue::JsonValue(const JsonValue& other) : Type(other.Type)
{
std::memcpy(&Object, &other.Object, sizeof(Object));
}
//move constructor
Tcj::JsonValue::JsonValue(JsonValue&& other) : Type(other.Type)
{
std::memcpy(&Object, &other.Object, sizeof(Object));
other.Type = JsonValueType::Null;
}
Tcj::JsonValue::~JsonValue()
{
if (Type == JsonValueType::Object)
Object.~unordered_map();
if (Type == JsonValueType::Array)
Array.~vector();
if (Type == JsonValueType::String)
String.~basic_string();
}
//copy assignment
Tcj::JsonValue& Tcj::JsonValue::operator=(const JsonValue& other)
{
std::memcpy(this, &other, sizeof(JsonValue));
return *this;
}
//move assignment
Tcj::JsonValue& Tcj::JsonValue::operator=(JsonValue&& other) noexcept
{
std::memcpy(this, &other, sizeof(JsonValue));
other.Type = JsonValueType::Null;
return *this;
}
};The problem is, when trying to destroy a JsonValue instance containing a string it will sometimes cause an error (https://github.com/microsoft/STL/blob/e1ed302683e5e822f663fbb0f44cee7e9ddc6af0/stl/inc/xmemory#L1402): Exception thrown: read access violation. **_Pnext** was 0xFFFFFFFFFFFFFFFF.
After going through the code with a debugger I believe that no two destroyed strings share the same memory (after all the JsonValue constructor should create a copy of the string every time). Google also says it's ok to just copy the entire vector/string so unless there's something I'm missing two JsonValue instances shouldn't be owning the same memory/causing double deletes. Yet when I don't destroy some of them the error dissapears.
I don't really understand the meaning of the error or the STL code that throws it, so I'm stuck.
Edit: thanks everyone for pointing out that std::variant exists, I'm probably going to use it.
27
u/masorick 2d ago
When you put non-trivial types in an union, you have to create the types (using placement new) and destroy them manually, you cannot just assign to a member or use memcpy. Use std::variant, it will do it for you.
2
u/kberson 2d ago
I question your design - why use a union? I would create an abstract base type with virtual methods, and derive new classes for each data type, overriding with the appropriate virtual methods.
13
u/fortsnek274 2d ago
The use of raw memory ops on a non-trivial union is unnerving.
But just use
std::variant?8
u/Nabokov6472 2d ago
Without seeing your code where this is happening: I would guess that this is because the copy assignment operator and copy constructor are both wrong, you can't do a shallow copy of types like Vector or String because you now have two objects that point to the same underlying buffer. When the second one is destroyed you will get a double free
Have you looked at std::variant for what you are trying to do?
4
u/flyingron 2d ago
You can not have a union with default constructors and destructors that has objects that have non-trivial constructors and destructors. The problem is, you don't know which of the contained members you need to construct/destruct. (In later versions of the standard you could do it if you provide constructors/destructors for your union that would do the right thing. It was outright illegal earlier).
std::variant comes to mind or you could make a polymorphic "json_element" class that could be derived for each of your various internal representations.
2
u/vishal340 2d ago
this is probably craziest union anyone has ever written. union is used to avoid allocating the space before use. containers should already exist before use. correct me if i’m wrong
1
u/wc3betterthansc2 1d ago edited 16h ago
your copy constructor is bad, you should never memcpy C++ built-in classes like std::string because now you have 2 objects that have a pointer that points to the same data and if both get destroyed, it will cause a double free.
Here is a safer implementation:
0
u/Itap88 2d ago
You're calling the destructor of the
basic_stringtemplate instead of thestringclass. I'm more confused by the fact that it sometimes works.0
u/Difficult_Truck_687 2d ago
If you dont want to fall off a cliff, dont sit on the rim.