Skip to main content
9 of 9
added 62 characters in body

PropertySet implementation

Problem Statement:

Store properties with a certain name (key), type and value. The key is always a character literal and the value can be of type bool, int, float, double, char, std::string, Vector2f, Vector2d, Color3f. It should be possible to add new properties and to retrieve the values of already existing properties. It is not expected that the value of a property is changed after adding it to the property set. The names of properties are unique and cannot be used multiple times (i.e. there can not be two properties with the same name). The number of properties is usually very low (less than 10 - it is not expected to have a high count here).

Implementation:

Here is a snippet of my proposed C++17 solution:

#include <map>
#include <sstream>
#include <string>
#include <variant>

...

class PropertySet {
public:
    template<typename ValueType>
    void addProperty(const std::string &name, const ValueType value) {
        if (hasProperty(name)) {
            // C++20 or https://github.com/fmtlib/fmt
            // std::string msg = std::format("Property with name '{}' does 
            // already exist and its value is '{}'", name, value);
            std::stringstream ss;
            ss << "Property with name '"
               << name.c_str()
               << "' does already exist and its value is '"
               << value << "'";
            throw PropertyDoesAlreadyExistException(ss.str());
        } else {
            values_[name] = value;
        }
    }

    template<typename ValueType>
    ValueType getProperty(const std::string &name) const {
        if (hasProperty(name)) {
            return std::get<ValueType>(values_.at(name));
        } else {
            std::stringstream ss;
            ss << "Property with name '" << name.c_str() << "' does not exist";
            throw PropertyDoesNotExistException(ss.str());
        }
    }

    // A defaultValue can be provided in the case a property does not exist
    template<typename ValueType>
    ValueType getProperty(const std::string &name,
                          const ValueType defaultValue) const {
        if (hasProperty(name)) {
            return std::get<ValueType>(values_.at(name));
        } else {
            return defaultValue;
        }
    }

    bool hasProperty(const std::string &name) const {
        return values_.find(name) != values_.end();
    }

private:
    typedef std::variant<bool,
            int,
            float,
            double,
            char,
            std::string,
            Vector2f,
            Vector2d,
            Color3f> VariantType;

    typedef std::map<std::string, VariantType> MapType;

    MapType values_;
};

Test 1:

TEST(PropertySet, TestBasicTypes) {
    PropertySet ps;

    ps.addProperty("a", 1);
    ps.addProperty("b", 1.0f);
    ps.addProperty("c", 1.0);
    ps.addProperty("d", 'a');
    ps.addProperty("e", std::string("Hello World!"));
    ps.addProperty("f", true);

    EXPECT_EQ(ps.getProperty<int>("a"), 1);
    EXPECT_EQ(ps.getProperty<int>("a"), 1);
    EXPECT_EQ(ps.getProperty<float>("b"), 1.0f);
    EXPECT_EQ(ps.getProperty<double>("c"), 1.0);
    EXPECT_EQ(ps.getProperty<char>("d"), 'a');
    EXPECT_EQ(ps.getProperty<std::string>("e"), "Hello World!");
    EXPECT_EQ(ps.getProperty<bool>("f"), true);
}

Test 2:

TEST(PropertySet, WhenPropertyDoesNotExist_Then_ReturnDefaultValue) {
    PropertySet ps;
    EXPECT_THAT(ps.getProperty("notExistingProperty", 42), ::testing::Eq(42));
}

Test 3:

TEST(PropertySet, WhenSamePropertyIsAddedTwice_ThenThrowExceptionAndExpectProperErrorMessage) {
    PropertySet ps;
    ps.addProperty("a", 1);

    EXPECT_THROW(ps.addProperty("a", 2), PropertyDoesAlreadyExistException);

    try {
        ps.getProperty<int>("a");
    }
    catch (PropertyDoesAlreadyExistException &ex) {
        EXPECT_THAT(std::string(ex.what()), 
                    ::testing::Eq("Property with name 'a' does not exist and its value is '1'"));
    }
}

I have more tests in place.

Questions:

Implementation

  • Should getProperty return a (const) reference?
  • Should I switch for name to const char* instead of using a std::string?
  • From a C++17 perspective: Are there more modern features of the language that I should use?
  • Should const T defaultValue be const T& defaultValue?
  • Is it clear from the name what is the idea of the defaultValue? Should I add a comment here?

Testing

  • Should Test 3 be split into two tests?
  • Should I use EXPECT_THAT everywhere (EXPECT_THAT vs EXPECT_EQ)?
  • Are there any edge cases for which the implementation fails?

Do you have other feedback, improvements for the implementation and testing?