1

Consider the following code (which is a simplified version of the original one):

#include <string>

namespace my
{
    class CType;
}

class my::CType
{
private:

    std::string simple_name;

public:

    explicit CType(std::string simple_name)
    : simple_name{std::move(RequireIdentifier(simple_name))} // Duplicate Code
    {
    }
    std::string get_simple_name() const
    {
        return simple_name;
    }
    void set_simple_name(std::string value)
    {
        simple_name = std::move(RequireIdentifier(value)); // Duplicate Code
    }
};

The RequireIdentifier function returns a reference to the value where the value is not empty otherwise it will throw an std::invalid_argument exception.

I have too many classes which are similar to the CType class definition, all of them have a private member variable of type std::string and subsequently they check its value to be not empty with the following expression:

std::move(RequireIdentifier(value))

Here is my problem, it is repeated in all of that classes! suppose that i have 10 classes similar to CType, then there should be 20=2*10 expressions like the above case! i don't want to repeat a similar code in all of the 20 places of code! it seams wrong.

I think that i should define a new class type:

class my::Identifier
{
private:

    std::string name;

public:

    explicit Identifier(std::string name)
    : name{std::move(RequireIdentifier(name))} // Duplicate Code
    {
    }
    std::string get() const
    {
        return name;
    }
    void set(std::string value)
    {
        name = std::move(RequireIdentifier(value)); // Duplicate Code
    }
};

And then change the definition of CType class as the following code:

class my::CType
{
private:

    Identifier simple_name;

public:

    explicit CType(Identifier simple_name)
    : simple_name{std::move(simple_name)}
    {
    }
    Identifier get_simple_name() const
    {
        return simple_name;
    }
    void set_simple_name(Identifier value)
    {
        simple_name = std::move(value);
    }
};

BTW, i can't avoid the repetitive std::move section and that is not a problem.

But if i don't overload the = operator of the Identifier class and if i don't declare an implicit conversion to the std::string for the Identifier class, then the Identifier class will not work as a string class. Then isn't it a bad choice of design? What is the proper way to avoid the repetitive code in such case (AFAIK, we shouldn't subclass from any STL container)?

EDIT: the RequireIdentifier function is defined as the following code:

inline std::string& my::RequestIdentifier(std::string& value)
{
    for(char c : value)
    {
        if(!isalnum(c) && c != '_')
        {
            throw std::invalid_argument{""};
        }
    }

    return value;
}

inline std::string& my::RequireIdentifier(std::string& value) // HERE IS IT
{
    if(value.empty())
    {
        throw std::invalid_argument{""};
    }
    else
    {
        return my::RequestIdentifier(value);
    }
}

Conclusion

As you can see if i put the "extraction" of the underlying string in my CType class (as mentioned in the accepted answer by Tony D), and if i want to work with the string type, then i have to call the get and set member functions of the Identifier class in all of my 10 classes repetitively as i had called the RequireIdentifier function. Therefore making a class just for eliminating one function call redundancy, techinically is not a good approach.

On the other hand and regardless of code redundancy, i should declare Identifer class if i really think that it is a new type that the string type cannot represent it well, and i can declare an implicit user-defined constructor for it to make it compatible with the string type, and finally the purpose of the Identifier class is not to be a get and set accessor for the string type, its purpose is to be a new type.

Sadeq
  • 7,795
  • 4
  • 34
  • 45
  • What is the reason function of `RequireIdentifier`? It seems a bit strange that it returns something you then attempt to move from. What happens if you move from the same lvalue twice? – juanchopanza Aug 18 '14 at 06:02
  • @juanchopanza: It is defined to avoid the repetition of checking if a string is empty. I included its definition. – Sadeq Aug 18 '14 at 06:09
  • A class with pure set/get functions are over engineered, just a struct should be enough? or just a string should be enough? – billz Aug 18 '14 at 06:12
  • @billz: that is my problem, i need to check that the string is not empty and can't just allow a string that can be empty! – Sadeq Aug 18 '14 at 06:14
  • @ccsadegh just write a free function for that should be sufficient? – billz Aug 18 '14 at 06:18
  • @billz: RequireIdentifier is a free function, but i had to repeat it every time when i have to set that private member variable. – Sadeq Aug 18 '14 at 06:20
  • @juanchopanza: It does, http://stackoverflow.com/questions/4819936/why-no-default-move-assignment-move-constructor – Sadeq Aug 18 '14 at 06:22
  • @ccsadegh Sorry, I somehow saw a user defined copy constructor in there. I better go back to sleep. – juanchopanza Aug 18 '14 at 06:24

1 Answers1

1

But if i don't overload the = operator of the Identifier class and if i don't declare an implicit conversion to the std::string for the Identifier class, then the Identifier class will not work as a string class. Then isn't it a bad choice of design? What is the proper way to avoid the repetitive code in such case?

So... just put the "extraction" of the underlying string in your CType class:

class my::CType
{
  private:
    Identifier simple_name;

  public:
    explicit CType(Identifier simple_name)
      : simple_name{std::move(simple_name)}
    { }

    std::string get_simple_name() const
    {
        return simple_name.get();
    }

    void set_simple_name(std::string value)
    {
        simple_name.set(std::move(value));
    }
};

UPDATE: as requested in comments, below - an illustration of using a check_invariants() function. There's still some redundancy, but you can repeat an arbitrary number of checks without further modifying each mutating function.

class my::CType
{
  private:
    std::string simple_name;

    void check_invariants()
    {
        if (!is_identifier(simple_name))
            throw std::invalid_argument("empty identifier);
    }

  public:
    explicit CType(std::string simple_name)
      : simple_name{std::move(simple_name))
    {
         check_invariants();
    }
    std::string get_simple_name() const
    {
        return simple_name;
    }
    void set_simple_name(std::string value)
    {
        simple_name = std::move(value);
        check_invariants();
    }
};

...with...

bool is_identifier(const std::string& s)
{
    return !s.empty() &&
        (isalpha(s[0]) || s[0] == '_') &&
        std::all_of(s.begin() + 1, s.end(), [](char c) { return isalnum(c) || c == '_'; });
}
Sadeq
  • 7,795
  • 4
  • 34
  • 45
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • Do you encourage to define a new class for repetitive get/set statements? – Sadeq Aug 18 '14 at 06:31
  • 1
    @ccsadegh These things are a matter of balance... defining a class to enforce invariants like this is often reasonable, though eliminating one line redundancy isn't compelling. A notable alternative is adding a `check_invariants()` function that you call at the end of the constructor(s) and assignment operator that can check any number of such conditions. Further, `get` and `set` are often indicative of an "anti-pattern" - it might be that such names could be keys in a map storing the values or any of many other models - it's hard to know without understanding your application.... – Tony Delroy Aug 18 '14 at 06:37
  • Would you include an example about check_invariants() function? – Sadeq Aug 18 '14 at 06:45
  • Thanks, the `check_invariants` function in this case seems good where the type of the member variable has a default ctor. – Sadeq Aug 18 '14 at 07:02
  • 1
    @ccsadegh: that's not even required, as the member variable can still be initialised in the initialisation list before the invariant/s is/are checked. – Tony Delroy Aug 18 '14 at 07:05
  • 1
    @ccsadegh I appreciate your efforts to create an answer you're happy with by editing further opinions into mine, but you should really post your own answer or make your own summary as an edit to the question. As is, I have my name to content I've not written and don't necessarily agree with. Edits should focus on correcting clear and significant accidental errors or omissions, adding links to already-mentioned content, fixing broken formatting so content appears as clearly intended. Or, when you have points to make you should make them as comments and let me decide whether to edit them in. – Tony Delroy Aug 20 '14 at 02:49
  • Sorry, you are right, and i should edit my question. Thank you for making me aware. – Sadeq Aug 20 '14 at 03:08
  • 1
    @ccsadegh: no worries - it's not that anything specific you'd written bothered me - just a tip for "SO harmony" :-). Cheers. – Tony Delroy Aug 20 '14 at 03:41