2

I am creating a strong binding of an XML specification and I am essentially trying to "flatten" the specification as much as possible, so that it won't feel like you are manipulating an XML tree.

There are many elements in the XML specification that are optional. Here is a contrived example.

<person>
   <name>Fred</name>
   <favorite-color>Blue</favorite-color>
</person>

In this the "name" element might be required, but the "favorite-color" element might say minOccurs=0 in the xsd making it optional.

I have two ideas of how to handle this, neither of which seems particularly elegant.

In Version1 you must test for nullptr before dereferencing getFavoriteColor's return to see if FavoriteColor is being used by your Person object. This seems like a bad approach because it makes it ridiculously easy to crash your program, especially because it might be hard to keep track of which members are optional and which are required. You may end up having to test everything for null all the time. This seems terrible.

In Version2 you can assume that the getters will not return nullptr's. You must check the "HasFavoriteColor" bool to find out whether or not FavoriteColor exists. But the tradeoff is that it seems to violate the rule of least surprise. You may setFavoriteColor( "Blue" ) then be surprised when your XML output doesn't show Blue because you forgot to setHasFavoriteColor( true ).

Which of these designs seems better, or at least more idiomatic? Is there a better solution?

Research: this is similar but doesn't fit https://stackoverflow.com/questions/3027067/optional-member-objects This is similar, but again doesn't seem to fit Checking for the presence of optional properties of an object

Also, I discovered that boost::optional exists, but that doesn't seem like a fit and I am not using anything but std and STL for my project.

(Assume "Name" and "FavoriteColor" below are user classes and not just typedefs of std::string.)

using Name = std::string;
using NamePtr = std::shared_ptr<Name>;

using FavoriteColor = std::string;
using FavoriteColorPtr = std::shared_ptr<FavoriteColor>;

/* Version 1: nullptr indicates FavoriteColor is absent,
 but Name will never be null because it is required */
class Person
{
public:
    Person()
    :myName( std::make_shared<Name>() )
    ,myFavoriteColor()
    {}

    NamePtr getName() const { return myName; }
    void setName( const NamePtr& value )
    {
        if( value )
        {
            myName = value;
        }
    }
    FavoriteColorPtr getFavoriteColor() const { return myFavoriteColor; }
    void setFavoriteColor( const FavoriteColorPtr& value ) { myFavoriteColor = value; }
private:
    NamePtr myName;
    FavoriteColorPtr myFavoriteColor;
};

/* Version 2: FavoriteColor and Name can both
 be trusted to not be null, but you have to
 check HasFavoriteColor bool to see whether
 the optional data is legit */
class Person2
{
public:
    Person2()
    :myName( std::make_shared<Name>() )
    ,myFavoriteColor( std::make_shared<FavoriteColor>() )
    {}

    NamePtr getName() const { return myName; }
    void setName( const NamePtr& value )
    {
        if( value )
        {
            myName = value;
        }
    }
    FavoriteColorPtr getFavoriteColor() const { return myFavoriteColor; }
    void setFavoriteColor( const FavoriteColorPtr& value )
    {
        if ( value )
        {
            myFavoriteColor = value;
        }
    }
    bool getHasFavoriteColor() const { return myHasFavoriteColor; }
    void setHasFavoriteColot( const bool value ) { myHasFavoriteColor = value; }
private:
    NamePtr myName;
    FavoriteColorPtr myFavoriteColor;
    bool myHasFavoriteColor;
};
4
  • 1
    Small point: "You may setFavoriteColor( "Blue" ) then be surprised when your XML output doesn't show Blue because you forgot to setHasFavoriteColor( true )." Solution: add a mHasFavoriteColor = true; in the setFavoriteColor() function. The advantage of setters over direct member access is that functions can maintain class invariants.
    – Sjoerd
    Commented Mar 19, 2015 at 15:29
  • Yes I thought about that as well but it violates another principle because it is a function with side effects. I may be suffering from a bit of analysis paralysis and probably need to just make a decision and blunder forward building something that works. Commented Mar 19, 2015 at 16:23
  • Why doesn't boost::optional seem like a fit? To me that is exactly what you are after. Can you elaborate why you don't want to use it?
    – valenterry
    Commented Mar 19, 2015 at 17:16
  • I prefer to use the language, standard library, and STL only for my project. I'm inexperienced and, frankly, I find it very challenging to build other people's projects that use a lot of boost and/or other third party libraries, so I want to use only the standard for this foundational class library. (BTW, this is entirely a personal project so we could say this is just a personal choice based on my current skill level. But for the sake of the SE question we could pretend no third party libraries are allowed by the "client") Commented Mar 19, 2015 at 17:26

2 Answers 2

2

Both of your designs have the problem that they put the burden of keeping track of the validity of the favoriteColor attribute on the user of the class. Your second design (with the getHasFavoriteColor) has the additional problem that it duplicates the information if there is a valid value for favoriteColor in a way that makes it very easy to create inconsistencies.

Of the two designs you have, the first one is the more idiomatic one.
Some other possibilities are:

  • Add a member bool hasFavoriteColor() const { return myFavoriteColor != NULL; }. It makes the check against NULL look better in the client code, but isn't really that different.
  • You could have a special 'empty' FavoriteColor object that gets returned when the user doesn't have a favorite color. That way you never have to return a NULL pointer.
2
  • 1
    The advantages of this hasFavoriteColor() is that it allows the getFavoriteColor() to return a reference instead of a pointer - by adding a precondition that the caller is responsible to check hasFavoriteColor() before calling getFavoriteColor() - while backing storage isn't duplicated. (of course, a decent implementation of getFavoriteColor() will include an assert to check this precondition).
    – Sjoerd
    Commented Mar 19, 2015 at 15:36
  • +1 I like this a lot "You could have a special 'empty' FavoriteColor object that gets returned when the user doesn't have a favorite color. That way you never have to return a NULL pointer." although I'm thinking that instead of having a special object for everything that can be NULL, "quasi-nullness" can just be a property of the FavoriteColor object. Commented Mar 19, 2015 at 16:40
0

I don't know C++ but I hope my idea helps you to resolve your problem.

Create your model class based on your XSD file.

class Person
{ 
   private string name;
   private string favoritColor;

  public Person (XmlElement person)
  {
    // parse the XML element and assign the values to appropriate element
    // if the element does not exist, don't assign the values to its equivalent element
  }

  public string GetName()
  {
     return name;
  }

  public bool HasFavoriteColor()
  {
     if (favoritColor ! = null)
     {
       return true;
     }
     return false;
  }

  public string GetFavoriteColor()
  {
    return favoritColor;
  }

}
3
  • 1
    There is a difference between 'empty' and 'not present'. Second, if it's a more complicated class, there might be no 'empty' representation.
    – Sjoerd
    Commented Mar 19, 2015 at 15:33
  • 2
    I strongly believe that there is no need for two separate classes for handling NULL and EMPTY representation. You could do those stuffs with a single class. Commented Mar 19, 2015 at 15:48
  • I appreciate this answer but it is off a bit from what I'm trying to do. Im actually wanting to author the xml files "from scratch". I'm not actually parsing or dealing with a DOM (although I may add parsing and DOM support later where the DOM is actually a facade). Commented Mar 19, 2015 at 16:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.