Recent

Author Topic: Need help to understand the operator overloading implementation in FPC  (Read 1092 times)

MathMan

  • Sr. Member
  • ****
  • Posts: 405
Re: Need help to understand the operator overloading implementation in FPC
« Reply #15 on: November 10, 2024, 05:12:52 pm »
@Warfley

Thanks for this explanation from the technical compiler perspective. To me it explains what I discovered so far and asserts that

 - I have not implemented something incorrect or sub-standard
 - I more or less have to live with the added overhead when using managed types and operator overloading

Cheers,
MathMan

Thaddy

  • Hero Member
  • *****
  • Posts: 16199
  • Censorship about opinions does not belong here.
Re: Need help to understand the operator overloading implementation in FPC
« Reply #16 on: November 10, 2024, 05:26:50 pm »
C++ for example has a move assignment, allowing to move the contents from one data structure to another and clearing the first one, which is incredibly useful for temporary objects like those returned by a construction function, to not have to do a deep copy but can just grab the pointer and clear
end;

Note: as you can see I cast the const away which is very bad practice, but it's a practical solution that works right now
So what is the difference? Yes, management operators can be slow on multiple assignments but C++ is no faster because it does the same. And a deep copy is mitigated the same way. I really, really do not understand your contribution. The comparison to C++ is a bad one, I mean. Don't take offence this time... Look at the language implementations at a much lower level. That is about the same...
« Last Edit: November 10, 2024, 05:30:04 pm by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

Warfley

  • Hero Member
  • *****
  • Posts: 1763
Re: Need help to understand the operator overloading implementation in FPC
« Reply #17 on: November 10, 2024, 07:28:51 pm »
C++ has the concept of an rvalue, r as in right hand side, denotes a value, denotes something that can be on the right hand side of an assignment basically a temporary object.

For example take the expression:
Code: Pascal  [Select][+][-]
  1. A = (B + C) * D
Here A, B, C and D are variables and therefore l-values as they are normal in language objects that can be assigned to. The operation B + C creates a new temporary object, which is then used to multiply with D, which then generates a new object that is then assigned to A. These are r-values. They are temporary objects that are only created on the right hand side of the assignment to be intermediate steps in the computation.

It get's clear when rewriting this as a set of instructions:
Code: Pascal  [Select][+][-]
  1. Add(out rval1, B, C);
  2. Mul(out rval2, rval1, D);
  3. Assign(A, rval2);

In the C++ rvalues are generally speaking the result of constructors and basic operators. The C++ typesystem allows to manage references to rvalues with &&, so (MyType &&) represents a reference to an rvalue of MyType.

This allows you to overload operators that only work on rvalues, most notable the move constructor:
Code: C  [Select][+][-]
  1. struct MyStruct {
  2.     MyStruct() {} // Default constructor
  3.     MyStruct &operator=(MyStruct &&fromStruct) {
  4.         std::cout << "Move temporary\n";
  5.         return *this;
  6.     }
  7.     MyStruct &operator=(MyStruct const &fromStruct) {
  8.         std::cout << "Copy from existing\n";
  9.         return *this;
  10.     }
  11. };
  12.  
  13. int main() {
  14.     MyStruct m1, m2, m3;
  15.     m1 = MyStruct{}; // Move temporary
  16.     m2 = m1;  // Copy from existing
  17. }

This allows for quite a bit more control, because in the rvalue constructure (move temporary), you know that the object will afterwards be destroyed. So you do not need to do a copy. So take this simple list implementation:

Code: C  [Select][+][-]
  1. struct MyList {
  2.   void **data;  // Will be freed in destructor
  3.   size_t len;
  4.  
  5.   MyList &operatator=(MyList &&fromList) { // RValue assignment: just take the data
  6.     free(this->data); // clear old data
  7.     this->data = fromList.data; // Grab old data from assignment
  8.     this->len = fromList.len;
  9.     fromList.data = nullptr; // Because we "stole" their data, set it to nil so it doesn't try to free our list
  10.   }
  11.   MyList &operatator=(MyList &&fromList) { // Copy assignment: create copy of data
  12.     this->data = malloc(sizeof(void*) * fromList.len);
  13.     memcpy(this->data*, fromList.data*, sizeof(void*)*fromList.len); // Make copy of data
  14.     this->len = fromList.len;
  15.   }
  16. };

The interesting thing now is, that when the object is a temporary object, we will not perform a copy of the data, but simply grab that data, and replace it with null, so only one pointer must be copied. But when you try to assign something that is persistent and will be usable later on, you must make a your own copy.

This can be combined with the C++ function move, which turns any object into an rvalue:
Code: C  [Select][+][-]
  1. MyList l1, l2;
  2. l1 = l2; // Copies l2
  3. l1 = std::move(l2); // moves the data from l2 into l1 without copying
But what is very important here is that after std::move, l2 is now not filled anymore, because the data has been extracted l2 is now empty and useless until it is initialized again.

 

TinyPortal © 2005-2018