Weapons are data. You shouldn't really ever keep data hardcoded. Your structs / classes should accommodate all necessary parameters to build a weapon's data from scratch, e.g.:
//etc. ad nauseam
And then say in text / .CSV:
And yes, you would typically load via a
LoadItems function called in
Init, but remember to make it separate since you may also have to call
LoadItems from elsewhere, such as
If some weapons have special properties such as doing multiple attacks
or a different effect would it be benefitial to make them the separate
classes to do it on an as-is basis instead of having every Weapon in
the game have that function except it doesn't do anything?
Nope. You should do it as a single class, as ultimately it's a finite set of options across all weapons in a given game, and doing it this way makes entering items easy (exception could be delegation much later; for now, do it as one class, factor out later according to SRP). The other reason is that dealing with combinations of static classes quickly becomes tricky for reasons I won't go into here. There really is nothing wrong with storing a bit of extra data for weapons, that is what CPU cache is for. And at worst, you could always compact your data, bitwise, later on.
If you're going to change options (members) often, you'll need to use a named format such as JSON / XML / YAML, otherwise you'll have to worry about changing order as in the CSV above, which does not mention WHAT each entry is, but only supplies them in-order (this is safer):
"name": "Mithril Sword",
"name": "Leather Whip",