Why are member variables called out?

Started by
19 comments, last by Bregma 5 years, 11 months ago

struct Array
{
  int* data;
  int size;
};

class WidgetSlow
{
  int sum;
public:
  void SetSum(const Array& arr)
  {
    sum = 0;
    for( int i=0; i!=arr.size; ++i )//read from an int via a pointer - must be done each iteration, because of the write to a member, below:
      sum += arr.data[i];//write to an int via a pointer - can invalidate any other int loaded via a pointer
  }
};

class WidgetFast
{
  int m_sum;
public:
  void SetSum(const Array& arr)
  {
    int sum = 0;
    for( int i=0; i!=arr.size; ++i )
      sum += arr.data[i];//write to a local does not invalidate other values loaded via pointers
    m_sum = sum;
  }
};

These two widgets do the same thing. The first one contains several performance pitfalls. The second one doesn't. The "m_" prefix helps make them visible.

Advertisement

class WidgetSlow
{
  int sum;
public:
  void SetSum(const Array& arr)
  {
    sum = 0;
    for( int i=0; i!=arr.size; ++i )
      sum += arr.data[i];
  }
};

class WidgetFast
{
  int sum;
public:
  void SetSum(const Array& arr)
  {
    int t_sum = 0;
    for( int i=0, end=arr.size; i!=end; ++i )
      t_sum += arr.data[i];
    sum = t_sum;
  }
};

... does the same job of distinguishing the scope of variables. And makes the 'SetSum' title a little more accurate (if we're being very strict), since we're not setting 'sum', we're setting m_sum.

But, again, I should have phrased the title of this post clearer to indicate that I am not questioning why we differentiate variables of different scopes, rather why we chose to call out the member variables, rather than anything out.

To be clear. We should differentiate between data at different levels of scope. Member variables, parameters and intermediary/temporary variables should be differentiated to avoid confusion. The question was why do we add the designator m_ to member variables, rather than p_ to parameter variables, or i_ or t_ to intermediary/temporary variables. I thought differentiating the parameter variables seemed more sensible, and assumed that member variables were differentiated as a convention, however, ChaosEngine has provided a good point as to why the differentiate should be where it is.

I used to put m_ in front of my member variables, but I stopped because I think it's an anti-pattern. The naming convention doesn't guarantees that this value doesn't come from somewhere else than expected. Instead of using a naming prefix, I use explicit code that is enforced by the compiler. For example, in C++, I know that a variable is local when I type the name of a variable only, and I know that a variable is a member variable because I explicitly type this.


class TimerA
{
	int m_timeSinceCreation;

public:
	TimerA()
	  : m_timeSinceCreation(0) {}

	void update(int deltaMS)
	{
	// m_timeSinceCreation can come from anywhere; it can be a member
        // variable, a global, another parameter or a local variable.
        // You can only be sure if you know the code.
		m_timeSinceCreation += deltaMS;
	}
};

class TimerB
{
	int timeSinceCreation;

public:
	TimerB()
	  : timeSinceCreation(0) {}

	void update(int deltaMS)
	{
		// timeSinceCreation is 100% guaranteed to be a member variable.
		this->timeSinceCreation += deltaMS;
	}
};

 

35 minutes ago, Michael Aganier said:

I used to put m_ in front of my member variables, but I stopped because I think it's an anti-pattern. The naming convention doesn't guarantees that this value doesn't come from somewhere else than expected. Instead of using a naming prefix, I use explicit code that is enforced by the compiler. For example, in C++, I know that a variable is local when I type the name of a variable only, and I know that a variable is a member variable because I explicitly type this.

Yeah "m_" is just a convention, but if you ever see a local / global variable named "m_*", you can immediately see that it's wrong... and you trust that your colleagues/self won't sabotage you by writing such obviously wrong code. Likewise though, if you can't trust your colleagues to not sabotage you with misleading code, then "this->" isn't a silver bullet either. e.g. the same person who is sneaky enough to put "m_" on a global might go ahead and write: 


int global;
struct Base
{
	struct { operator int&() { return global; } }member;
};

struct Foo : Base
{
	void update(int deltaMS)
	{
		// oops these are actually a global variables, not members
		this->timeSinceCreation += deltaMS;
		this->member += deltaMS;
	}
	
	static int timeSinceCreation;
};
14 hours ago, SomeoneRichards said:

But, again, I should have phrased the title of this post clearer to indicate that I am not questioning why we differentiate variables of different scopes, rather why we chose to call out the member variables, rather than anything out.

...however, ChaosEngine has provided a good point as to why the differentiate  should be where  it is

Yeah ChaosEngine makes a good point and I was trying to express something similar. The larger the scope of a variable, the bigger the consequences of interacting with it. In my example, interacting with members acts distinctly differently to interacting with locals, so IMHO the prefix is helpful in making these extra consequences visible.

On 5/19/2018 at 1:08 AM, SomeoneRichards said:

The question was why do we add the designator m_ to member variables, rather than p_ to parameter variables, or i_ or t_ to intermediary/temporary variables.

@Bregma made the point that fewer warts are better. Littering your code with prefixes doesn't aid readability. So it makes sense to add them to as few variables as possible. Open up any class and count the number of member variables versus locals/parameters. In general, the locals/parameters will significantly outnumber the members. So if you prefix i_ or t_ or p_ you're going to be prefixing many more variables. 

Finally, here's a simple practical consideration. What does m_ mean to you? To almost any C++ programmer, it's member.

But the others? If I saw i_ or p_, I'd probably assume some kind of systems Hungarian denoting integer or pointer. (t_ doesn't really mean anything... template type??).

if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

I have worked at a company which mandated the use of "a_" for function arguments (similar to the suggestion of "p_" for function parameters), but I wasn't a fan / didn't really see the benefits.

On my own code-base, I do use "out_*" for function arguments that act as extra return values, because these really are quite different to other arguments (similar to the out keyword in C#).

Some compiler can also do :


	void myclass::func( float x , float y )
	{
	x = x;
	y = y;
	}
	

Would you use that ?

S T O P C R I M E !

Visual Pro 2005 C++ DX9 Cubase VST 3.70 Working on : LevelContainer class & LevelEditor

7 hours ago, the incredible smoker said:

Some compiler can also do :

 



	void myclass::func( float x , float y )
	{
	x = x;
	y = y;
	}
	

 

Would you use that ?

I'm very skeptical of that. Are you claiming that x and y are both members and parameters? How would the compiler know which x is the parameter and which is the member? Does it assume that lvalues are members? Can you tell me which compiler does that so I can avoid it like the plague?

Even on the off chance you could do that, don't. There is nothing good about that code. It's confusing, ambiguous and definitely non-portable. 

if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight
7 hours ago, the incredible smoker said:

Some compiler can also do :

 



	void myclass::func( float x , float y )
	{
	x = x;
	y = y;
	}
	

 

Would you use that ?

No, that just assigns the arguments to themselves. The only place this works is in constructor initializer lists:


struct foo { 
  int x, y; 
  foo(int x, int y)
    : x(x), y(y) {} };
//copies the arguments to the members 

 

On 5/18/2018 at 9:08 AM, SomeoneRichards said:

The question was why do we add the designator m_ to member variables, rather than p_ to parameter variables, or i_ or t_ to intermediary/temporary variables.

Function parameters are just function-local variables initialized with the value of the function arguments.  You could prefix them with l_ for local, but remember it's hard to distinguish between the letter ell and the numeral one in many typefaces.

As to why we call out variables that affect current object state in C++ code and not the traditional local variables used in C code from the 1970s and later, well, it's both inertia (millions of lines of C code have been written over the decades, which is why the ever-so-compatible C++ language is popular) and an improvement to reasoning about the code, since modifying a member variable locally can have repercussions elsewhere, whereas modifying a local variable is, well, pretty much local by definition.  Pretty much the same reason you would make the Big Red Button big and red when all the other buttons are just regular-sized and probably not red, instead of the other way around.

Stephen M. Webb
Professional Free Software Developer

This topic is closed to new replies.

Advertisement