Is it a very bad idea for a class to pass (*this) into a function? Strange bug...

Started by
14 comments, last by Sean_Seanston 9 years, 2 months ago

Just wondering if it's a known source of errors for a class to do something like:


void ClassA::functionA()
{
     variableX = functionB( classA1, (*this) );
}

???

Because I was experiencing a very strange error last night when trying something like that, and it turned out the problem went away if I stopped passed (*this) and instead e.g. created another object of type ClassA that copied the values of (*this) and passed that in instead.

I admit it does seem like a strange, probably sloppy, construction, but I've never heard of it being something to avoid and if it was so specifically bad I would've expected some form of compiler error/warning either when compiling or running/debugging. The problem definitely seems to come from solely passing (*this), even when the function does nothing.

The bug I get then is quite puzzling to me, because it seems to be affecting things that are almost unrelated... the only thing I can figure is that an object passing itself like this might be somehow able to cause some kind of screw up/corruption with that object's data members in some circumstances. At first I thought maybe some kind of infinite loop but the program is still running.

So is this known to cause unexpected behaviour and to be avoided like the plague, or should I get into explaining my code quite specifically?

Also, I know that "this" is a pointer... and since * is used to dereference a pointer, does that mean passing (*this) is pass-by-value? That was my assumption at least, and hence I can't figure out why it would be a problem and passing in an object with the same values isn't. Unless it's some kind of pass-by-reference?

Advertisement

There is something else going on. C++ programs use *this all the time.


Vector2d& Vector2d::operator= (const Vector2d& param)
{
  x=param.x;
  y=param.y;
  return *this;
}

There should be no problem pass a (this*) to a function.


Also, I know that "this" is a pointer... and since * is used to dereference a pointer, does that mean passing (*this) is pass-by-value?

As far as this part goes, it can be confusing. C++ function arguments are always "pass-by-value". Everything that goes into a function is copied. That's why you pass a pointer or reference instead of an object. It can be a lot cheaper to copy a 32bit pointer than a huge object. For instance, when you pass a pointer and then access the thing it points to, you are using a copy of the pointer that points to the same object.

As an example, what does this do?


void func( int* intPointer ) {
   intPointer = new int{2};
}

void test() {
   int x = 4;
   int* intPointer = &x;
   func( intPointer );
   std::count << *intPointer << std::endl;
}

If the int* was passed by reference, then we have changed it to point to a different value. Is that what happens?

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

There's nothing wrong with dereferencing the this pointer, your error has most likely something to do with your code(I'm guessing the second parameter is of type ClassA&).


Also, I know that "this" is a pointer... and since * is used to dereference a pointer, does that mean passing (*this) is pass-by-value?

That depends on the argument type in functionB, it could be by value, reference, const reference etc.

There is something else going on. C++ programs use *this all the time.


Vector2d& Vector2d::operator= (const Vector2d& param)
{
  x=param.x;
  y=param.y;
  return *this;
}

There should be no problem pass a (this*) to a function.


Also, I know that "this" is a pointer... and since * is used to dereference a pointer, does that mean passing (*this) is pass-by-value?

As far as this part goes, it can be confusing. C++ function arguments are always "pass-by-value". Everything that goes into a function is copied. That's why you pass a pointer or reference instead of an object. It can be a lot cheaper to copy a 32bit pointer than a huge object. For instance, when you pass a pointer and then access the thing it points to, you are using a copy of the pointer that points to the same object.

As an example, what does this do?


void func( int* intPointer ) {
   intPointer = new int{2};
}

void test() {
   int x = 4;
   int* intPointer = &x;
   func( intPointer );
   std::count << *intPointer << std::endl;
}

If the int* was passed by reference, then we have changed it to point to a different value. Is that what happens?

Just as a point but a pointer is not necessarily 32 bits and you should never ever write code that actually relies on the exact size of a pointer. This is what causes a lot of bugs in software when switching from 32-bit to 64-bit.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion


Just as a point but a pointer is not necessarily 32 bits and you should never ever write code that actually relies on the exact size of a pointer. This is what causes a lot of bugs in software when switching from 32-bit to 64-bit.

Yes, you're right. Good point.

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

What is the signature of functionB()? If functionB does takes ClassA by value, is the class safely copyable, i.e. obeys the rule of three?

What is the signature of functionB()? If functionB does takes ClassA by value, is the class safely copyable, i.e. obeys the rule of three?


and lets not forget about slicing.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

a) C++ reference types are references (hence why they're called references and not "magic pointer value thingies") and everything else is an object (aka value). Simple.

b) *pointer where `pointer` is an actual pointer will dereference the pointer. The result is always an lvalue reference. `this` is always an actual pointer. Hence *this is always a reference, not an object. Simple.

c) *expression where `expression` refers to an object with an overloaded operator*() can have any type. The type of *expression is determined by the return type of the overloaded operator*(). Simple.

d) A pointer is an object that stores the address of another object. If objects are not references and pointers are objects then pointers are not references. Simple.

e) A function's signature determines how things are passed to it. A function parameter of reference type is passed by reference. A function parameter of object type is passed by value (copied into a new object). Simple.

Part (d) is important to call out as it's explained incorrectly too often. The C++ standard never explicitly states nor implicitly guarantees that a reference is like a pointer. There are such things as C++ interpreters, static analyzers, and so on that do not treat a reference as a pointer-like sugar as your compile-to-raw-hardware tool chain (probably) does. References are often implemented as a memory address on hardware like a pointer is implemented on hardware, but that's an implementation detail. That implementation detail is relevant here or there when you're debugging particularly bad bugs on real hardware or when you're writing non-portable over-optimized too-clever-for-your-own-good code, but that's it.

Sorry to be so pedantic, but this stuff matters. People think C++ is hard because they're taught bags of broken metaphors and one-off examples instead of the basic foundations of _why_ things work. e.g. templates are _so simple_, but they're usually taught incompetently and hence everyone gets scared off by them.

Sean Middleditch – Game Systems Engineer – Join my team!

There should be no problem pass a (this*) to a function.

Interesting... must be something strange going on then. I'll elaborate and describe the situation properly later. I was just hoping it would be something like that so I wouldn't have to track down this apparent weirdness unsure.png .

As far as this part goes, it can be confusing. C++ function arguments are always "pass-by-value". Everything that goes into a function is copied. That's why you pass a pointer or reference instead of an object. It can be a lot cheaper to copy a 32bit pointer than a huge object. For instance, when you pass a pointer and then access the thing it points to, you are using a copy of the pointer that points to the same object.

Hmm... so you'd have to pass a pointer by reference I assume in order to change what it points to from within a function?

What is the signature of functionB()? If functionB does takes ClassA by value, is the class safely copyable, i.e. obeys the rule of three?

Let's see... apart from 2 static members (which I assume are irrelevant) it's composed of ints, a bool, 2 custom structs that are just ints/bools and... aha! I think we might be onto something here... the last 3 data members are all instances of my custom BitmapFont class, which is actually what the problem is centred around in the first place.

What I find strange though is that the problem is occurring JUST BY PASSING (*this) into the function, even when that function is empty or at least not doing anything in any way with the argument corresponding to (*this).

I suppose that probably just makes it easier to debug tho, narrowing things down...

b) *pointer where `pointer` is an actual pointer will dereference the pointer. The result is always an lvalue reference. `this` is always an actual pointer. Hence *this is always a reference, not an object. Simple.

This must have something to do with it then...

Though if anything, I would have thought that being a reference would make the chance of e.g. things being copied wrong, less likely.

Anyway, proper description of the situation coming up... just hope I can be concise...

I've been building a system for keeping track of a game's date and time based upon the real-life Gregorian calendar. So you might have a strategy game where you want to display the date and time as "04/02/2043 12:43", and as time passes the system will accurately keep track of the number of days in a month including leap years etc.

To do this I have 3 main components:

A struct called Time which keeps track of seconds, minutes and hours.

A struct called Date which does the same for days, months and years.

A class called GameDateTime which has Time and Date structs and is used to coordinate the whole thing, e.g. when the time is 23:59:59 and advances one second, the time will automatically reset to 00:00, but GameDateTime ensures that this causes its Date object to also advance by 1 day.

GameDateTime also has 3 objects of the class BitmapFont, which are used to display the time, date and ticks since the epoch time of the gameworld. Maybe it isn't ideal for them to be here in the first place, or maybe it's alright, either way I mostly just have them there for convenience while I'm working on implementing the whole system and checking that it works.

The problem is:

- I have a function in GameDateTime called setDateTime. It takes a Date and a Time struct, the idea being to be able to change a GameDateTime's date and time values to any arbitrary value (and still keep track of time since the epoch etc.).

- In order to get the epoch counter right, I need to know the time between the new date/time and the epoch time/date. To do this I made a static function in GameDateTime called getMinsBetweenGameDateTimes, which takes 2 GameDateTime objects and works out the time between them in minutes.

- As far as I can see, everything is working as it's supposed to be in terms of mathematics etc., and getMinsBetweenGameDateTimes seems not to cause any problems when given 2 GameDateTime objects that don't involve (*this), like I've said.

- However, when a GameDateTime object's setDateTime() is called (I know because I was testing by using a "Y" keypress to make the date jump forward by a year) and it in turn calls getMinsBetweenGameDateTimes( epochDateTime, (*this) ), the text of all 3 BitmapFont objects instantly disappear and don't return.

Make sense to anyone?

I find it strange because none of the functions that get called appear to have anything directly to do with the BitmapFont objects (they receive their values in a separate draw() function) and I can't understand that they'd be somehow getting overwritten with improperly set up objects. I would understand maybe if passing (*this) was somehow, through improper copying, corrupting the values of the BitmapFont objects that the function would then be accessing, but the function only ever deals with the Time and Date structs of the GameDateTime objects.

Could passing (*this) somehow be causing the object's data members to actually change in some way? Well, any of my theories are probably going to be way off...

EDIT: I see it also happens if I call something as simple as

GameDateTime gdt = (*this);

This topic is closed to new replies.

Advertisement