template accessors for class?

J

Joe Van Dyk

Say I have the following class:

using std::string;
class Player
{
public:
Player() : name(""), age(""), other_stuff("") {}
private:
string name;
string age;
string other_stuff;
};


It's "proper" to create setter and getter methods for each of those
private data members, right?

In Ruby, the same class plus all the accessor methods could be
represented as:

class Player
attr_accessor :name, :age, :eek:ther_stuff
def initialize
@name = ""; @age = ""; @other_stuff = ""
end
end

Where the attr_accessor method takes symbols (in short, immutable
strings) and dynamically generates a function that serves as a getter
and setter for each one of those data members...

class Player
def name=(n)
@name = n
end
def name()
return @name
end
# and so on
end


Can I do something like that with templates? I'm finding that I'm
repeating myself a bunch when doing these accessor methods in C++.

Thanks,
Joe
 
I

Ian Collins

Joe said:
Say I have the following class:

using std::string;
class Player
{
public:
Player() : name(""), age(""), other_stuff("") {}
private:
string name;
string age;
string other_stuff;
};


It's "proper" to create setter and getter methods for each of those
private data members, right?
Not in my book, it's a design smell.

If all you require is a bucket of values, use a struct.

If the object has some other use, then why provide accessors?
 
D

Daniel T.

Joe Van Dyk said:
Say I have the following class:

using std::string;
class Player
{
public:
Player() : name(""), age(""), other_stuff("") {}
private:
string name;
string age;
string other_stuff;
};


It's "proper" to create setter and getter methods for each of those
private data members, right?

Proper? Well it's not evil or anything like that but proper? Who's going
to call them and why? Once you know the answers to those two questions,
then you can say whether or not they are proper.
 
J

Joe Van Dyk

Ian said:
Not in my book, it's a design smell.

If all you require is a bucket of values, use a struct.

If the object has some other use, then why provide accessors?

I thought the point was that /now/ it's just a struct, but later on, it
could be a complex class that seeks world domination, and as such, would
need to hide its privates so it could change how they are stored later on.

Joe
 
I

Ian Collins

Joe said:
I thought the point was that /now/ it's just a struct, but later on, it
could be a complex class that seeks world domination, and as such, would
need to hide its privates so it could change how they are stored later on.
That would happen as if by magic once you changed it from a struct to a
class!
 
V

Victor Bazarov

Ian said:
That would happen as if by magic once you changed it from a struct to
a class!

If you allow me to butt in...

Having accessors is not yet the end of the world. However, the OP should
think first and foremost about the interface to the class, and not about
the data that are stored in.

Joe,

For some reason you decided to begin with the class implementation. That
is not right. I think I know why it's so. You might think: "OK, I have
a player. What properties does a player have? It has a name, its age,
and some other stuff. Let's give it those data members, and proceed from
there." It started OK, it only progressed in the wrong direction. Let's
begin again. You have a "player". In your model domain a "player" has
a "name". What for? So it can be identified by it, right? So, the class
'Player' needs a way so that somebody can query the "name" of an instance.
And we can safely establish that whoever asks for the "name" expects no
less (and no more) than a 'std::string' object.
So we write

class Player {
public:
std::string name() const; // I made it const because it's unlikely
// to change anything in the object
};

That's our first step in the design process. What's next? How does each
'Player' instance get its "name"? How did you get your name? Your parents
(most likely) named you when you were born. So, it makes sense to give
the class 'Player' a constructor with an argument, which will designate
the instance being constructed with a name:

class Player {
public:
Player(std::string name);
std::string name() const;
};

OK, good. As some would suggest, you need to make a test case immediately
for that functionality to provide the test platform and an example of how
to use the class:

int main() {
Player player("Joe");
assert(player.name() == "Joe");
}

If you combine the two pieces, the program won't compile yet, but it will
be very close. What do you need now? You need to _implement_ those two
functions (the constructor and the 'name' member). Only *now* do you get
to pick _how_ the "name" of a 'Player' instance gets stored. For all we
know, you could actually have a global table of names and each player
would store an index in that table...

But let's not overcomplicate things. Let 'Player' have a data member,
so it can store its own name and give it when asked:

class Player {
std::string n;
public:
Player(std::string name);
std::string name() const;
};

If that's our initial layout of the 'Player' class, let's now create the
bodies of the member functions:

Player::player(std::string name) : n(name) {
// nothing here
}

std::string Player::name() const {
return n;
}

Why did we call the data member 'n'? No reason. Call it what you will.
It's only important to _you_, not to any user of that class.

Now, let's just evaluate a couple of things here. Is a 'Player' allowed
to change its name? Generally, yes. In real life, anybody can change
their name. The only limitation is (a) only after 18 years of age and
(b) by applying to a judge, and don't forget to give good enough a reason
for the name change.

So, why not limit [y]our class in that regard and prohibit it from ever
changing the name of an instance? I say, let's. How? We can make the
'n' member constant and not create any functionality to change the value
of that member during the lifetime of the object:

class Player {
const std::string n;
public:
Player(std::string name) : n(name) {}
std::string name() const { return n; }
};

What else can we improve here? Not much, but I'd do two things. First
of all, let's (for now, until we have a good reason not to) prohibit
implicit conversion from 'std::string' to 'Player' by declaring it one-
argument constructor 'explicit', and let's for the sake of keeping good
habits, pass the argument to the constructor by a reference to const:

class Player {
const std::string n;
public:
explicit Player(std::string const & name) : n(name) {}
std::string name() const { return n; }
};

That's it. We're done so far with the name. Now we can proceed to
the 'age'.

What's important about the age? If a 'Player' were a model of a real-
life person, at creation its age would be 0. And then, as 'time' would
pass in the simulation ("program"), each player would age accordingly.
Well, when we're born, we're rarely players, so we cannot model [y]our
'Player's as if they were real people. However, we could follow the
same logic as with the name. For the duration of the game, the 'age'
is unlikely to change. So, it is probably logically correct to prohibit
any changes to 'age' during the lifetime of a 'Player' object. And when
does it get its age? We should provide the mechanism to give every
'Player' some 'age', and the most logical time is during *construction*
(again)...

I'll leave this exercise to you. As soon as your 'Player' has two (and
not one) arguments in its constructor, you don't need "explicit" any
longer, but it shouldn't hurt. Don't forget to write a proper test for
'age' as well.

Good luck!

V
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,763
Messages
2,569,563
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top