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.