Kammeot
Kammeot

Reputation: 469

C++ unordered_map with custom key resulting in null values

I'm using a std::unordered_map with keys of Currency and values with a double of the currency price. Currency is a custom class that I made. Here is one version that I have tried:

#ifndef CURRENCY_H
#define CURRENCY_H

#include "Nameable.h"
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/functional/hash.hpp>
#include "BigDecimal.h"
#include <iostream>

/**
 * Represents a single currency. Can be used as keys in a map and as a general
 * identifier for determining what unit a value of money is.
 * @param name
 */
class Currency: public Nameable {
public:
    Currency(std::string name) throw(NameAlreadyTakenException);
    Currency(const Currency& orig);
    virtual ~Currency();
    virtual std::string getName();
    virtual void setName(std::string name) throw(NameAlreadyTakenException);
    inline bool operator==(const Currency& key) const {
        return this->id == key.id;
    }

    // A custom hasher that I tried using.
    struct currencyHasher
        {
        std::size_t operator()(const Currency& k) const
        {
            return boost::hash<boost::uuids::uuid>()(k.id);
        }
    };
    boost::uuids::uuid id;
private:

};
// A template specialization for Currency. 
namespace std {
    template <>
    struct hash<Currency> {
        std::size_t operator()(const Currency& k) const {
            cout<< boost::hash<boost::uuids::uuid>()(k.id)<<"\n";
            return boost::hash<boost::uuids::uuid>()(k.id);
        }
    };
}
#endif  /* CURRENCY_H */

And here is the implementation:

#include "Currency.h"

Currency::Currency(std::string name) throw(NameAlreadyTakenException) {
    this->setName(name);
    this->id = boost::uuids::random_generator()();
}

Currency::Currency(const Currency& orig) {

}

Currency::~Currency() {
}

std::string Currency::getName() {
    return this->name;
}

void Currency::setName(std::string name) throw(NameAlreadyTakenException) {
    this->name = name;
}

I tried making Currency key-compatible by implementing both suggestions given by the answer to: C++ unordered_map using a custom class type as the key. As you can see I've overridden the operator== as well as providing a custom hasher as well as specialize the template.

Despite all of this, the keys seem to be losing the values. By this I mean doubles, floats and ints get turned into 0 and string get turned into empty strings. Of course, it causes other problems with anything else that I use as a value. For example:

Currency dollar("Dollar")
std::unordered_map<Currency,int,Currency::currencyHasher> currenMap;
currenMap[dollar]=1337;
std::cout<<currenMap[dollar]<<"\n";

The output of this in the console is 0. Utilizing the template specialization doesn't work either:

std::unordered_map<Currency,int> currenMap;
currenMap[dollar]=1337;
std::cout<<currenMap[dollar]<<"\n";

produces a 0 as well...

Could the fact that Currency is a subclass of Nameable be causing problems? I'm using the boost::uuid as the hash (utilizing boost::hash<boost::uuids::uuid> to convert the id into a size_t) I'm not sure what I'm missing, I thank you for your help.

Upvotes: 1

Views: 2025

Answers (1)

Barry
Barry

Reputation: 302842

The issue is with the copy constructor:

Currency::Currency(const Currency& orig) {

}

When you copy a Currency, you get a default-constructed id. When you insert a Currency into the map, it gets copied, and that copy will have a different id than the original. Thus this:

currenMap[dollar]=1337;

is effectively adding {Currency(), 1337} into the map. So when you are looking up the one with whatever id gets created for dollar, it won't be there. It's not that the value gets "zeroed out"... it's that you get a default-constructed value back.

Fixing your copy constructor should fix the problem.

Upvotes: 2

Related Questions