Passing classes to functions
Hi All, I was doing some work and created some bugs that I ironed out when I discovered something: when non-primitive types are passed to functions they are passed by reference and not by value! I include a test so you can see what I mean. I hope somebody find this useful. --art class test{ 0 => int val; } test obj1; // obj2 is a reference to obj1 test obj2 @=> obj1; <<< obj1.val , "original object">>>; <<< obj2.val , "reference to original">>>; 2 => obj2.val; <<< obj1.val , "original object">>>; <<< obj2.val , "reference to original">>>; fun void testclasspassing(test i) { <<< i.val, "in function" >>>; } testclasspassing(obj1); testclasspassing(obj2);
Art:
Maybe I don't understand your intention, but the code works as I would
expect. Consider these lines:
[1] test obj1;
[2] // obj2 is a reference to obj1
[3] test obj2 @=> obj1;
Line [1] instantiates a test object. Line [3] instantiates second test
object and clobbers the obj1 reference, with the result that obj1 and obj2
really and truly reference the same piece of memory. Changing the contents
of "the thing that obj1 references" will also change the contents of "the
thing that ob2 references", since they are the same object.
Thus the results that I see appear correct:
bash-3.2$ chuck adam.ck
0 original object
0 reference to original
2 original object
2 reference to original
2 in function
2 in function
Am I missing something?
- Rob
On Fri, Oct 2, 2009 at 00:38, Adam Tindale
Hi All,
I was doing some work and created some bugs that I ironed out when I discovered something: when non-primitive types are passed to functions they are passed by reference and not by value! I include a test so you can see what I mean.
I hope somebody find this useful.
--art
class test{ 0 => int val; }
test obj1; // obj2 is a reference to obj1 test obj2 @=> obj1;
<<< obj1.val , "original object">>>; <<< obj2.val , "reference to original">>>;
2 => obj2.val;
<<< obj1.val , "original object">>>; <<< obj2.val , "reference to original">>>;
fun void testclasspassing(test i) { <<< i.val, "in function" >>>; }
testclasspassing(obj1); testclasspassing(obj2); _______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
Robert;
Am I missing something?
I don't think so. I think Adam -like me a while ago- just hadn't realised the exact relationship between objects and primitives and now discovered it on his own. If we exclude the possibility that Adam and me are completely backwards and disinterested in the nature of ChucK or at least oblivious to the docs I'd say this means this subject needs more explicit clarification in the manual. We now know a lot more about objects, assignment and casting than we did -say- a year ago (at least as the relate to ChucK) but the manual lacks a comprehensive section on the matter. Writing this would not be all that simple as a straightforward and brief explanation from a CS perspective wouldn't do because we have to assume readers of the manual might be encountering these terms for the first time. Does anybody feel called to do a draft on the WiKi? Yours, Kas.
On 2 Oct 2009, at 09:38, Adam Tindale wrote:
I was doing some work and created some bugs that I ironed out when I discovered something: when non-primitive types are passed to functions they are passed by reference and not by value! I include a test so you can see what I mean.
I hope somebody find this useful.
Though it has been pointed out briefly as being the normal for a typical reference implementation, it might help to linger a bit on the details, and compare with the situation when a reference count or other GC (garbage collector) is present. - Some say chuck might get a ref count someday.
class test{ 0 => int val; }
Here, one typically implements two classes, this class test, and another (using pseudo-code) class test_ref { test* pointer_; }; If one uses a reference count, the class test will have an additional integer, hidden from public view, indicating how many copies there are: class test { private: int count_; public: 0 => int val; }
test obj1;
This produces the implementation: test_ref obj1; obj1.pointer_ = new test(); obj1.count_ = 1; // If reference count is used.
// obj2 is a reference to obj1 test obj2 @=> obj1;
Now, this is equivalent to the implementation: test_ref obj2; obj2.pointer_ = new test(); // By test obj2 obj2.count_ = 1; // If reference count is used. obj1.pointer_ = obj2.pointer_; // By the @=> Since chuck does not have a ref count, the original obj1.pointer_ now is referenced by nothing. So it is a memory leak. A so called tracing GC (garbage collector) can find this out by starting from the set of objects that originates all other objects: the function stack and global objects, which is called the root system, and follow all pointers. The objects not found in such a sweep are viewed as dead, and removed. When using a reference count, one needs to check if obj1.pointer_ and obj2.pointer_ already point to the same object, and if they do, do nothing - assignment does not change anything. Otherwise decrease the reference count of obj1.pointer_, and if it becomes 0, remove it. Then increase the reference count of obj2.pointer_ and set obj1.pointer_ = obj2.pointer_.
<<< obj1.val , "original object">>>; <<< obj2.val , "reference to original">>>;
2 => obj2.val;
<<< obj1.val , "original object">>>; <<< obj2.val , "reference to original">>>;
fun void testclasspassing(test i) { <<< i.val, "in function" >>>; }
This will be implemented as void testclasspassing(test_ref i) { <<< i.pointer_->val, "in function" >>>; }
testclasspassing(obj1); testclasspassing(obj2);
So this then becomes clear: obj1.pointer_ is the same as obj2.pointer_; therefore testclasspassing() prints the same object. (Since the pointer can be 0, then implementation might benefit from some check.) There is a memory leak, that would have been prevented by the use of a reference count or other GC. Hans
On 2 Oct 2009, at 20:43, Kassen wrote:
Since chuck does not have a ref count,
As far as I know it actually does, ...
That is right, the base class Chuck_VM_Object has it, which suggest common reference types have it.
...it's just not fully implemented yet...
OK. So then all that discussion on how avoiding leaks were unnecessary. Do you what isn't reference counted?
...and under some conditions the reference count can be off, leading to the collection of objects that aren't yet garbage.
That would bomb, so hopefully chuck does not have it. Reference counts can't collect circular object. But then you need a way to create them, as in linked lists. And if a circular object has destructors, it is anyway unclear in which order they should be called. Hans
Hi Everyone, This is a more direct version of what I meant to say. The problem I encountered was that I was hoping to do some operations in a function and was hoping that I was passing a copy of an object, much like what happens with primitives. Since objects pass by reference then weird side effects and bugs can easily creep into a fellow ChucKist's code. As for the manual... it is badly out of date. I have been talking with Ge about unifying the docs and making contributions from the list easier to integrate. The mailing list is an incredible wealth of knowledge, but hard to parse for the new ChucKist. I'm hoping to organize a documentaiton sprint around Christmas when some more of these hack pacts are done, so we can integrate some of the great things coming out of that. Anyways, I hope this example is more clear. --art class test{ 0 => int val; } test obj1; <<< obj1.val , "original object">>>; fun void testclasspassing(test i) { 4 => i.val; <<< i.val, "in function" >>>; // side effect! } testclasspassing(obj1); <<< obj1.val , "original object">>>;
On 4 Oct 2009, at 02:21, Adam Tindale wrote:
This is a more direct version of what I meant to say. The problem I encountered was that I was hoping to do some operations in a function and was hoping that I was passing a copy of an object, much like what happens with primitives. Since objects pass by reference then weird side effects and bugs can easily creep into a fellow ChucKist's code.
Yes, that is one side effect of it: side effects. The reference objects reference the same object on the heap, also when passed into a function.
Anyways, I hope this example is more clear.
--art
class test{ 0 => int val; }
test obj1; <<< obj1.val , "original object">>>;
fun void testclasspassing(test i) { 4 => i.val; <<< i.val, "in function" >>>; // side effect! }
testclasspassing(obj1); <<< obj1.val , "original object">>>;
So one way to think about it, is that this is the same as C/C++ void fun void testclasspassing(test* i) { 4 => i->val; <<< i->val, "in function" >>>; // side effect! } which in C++ can also be written as fun void testclasspassing(test& i) { 4 => i.val; <<< i.val, "in function" >>>; // side effect! } only that in chuck, the extra reference symbols are hidden away by the implementation of the reference object. So when mutating the object within the function, as it is a reference to an object on the heap, it will be mutated there too. By contrast, when one has a copy over type, a copy will be made, which will placed in the function stack. So when changing that within the function, that is a copy, and not the original object. Hans
I think you can pretty much say that ChucK follows Java's attitude towards
objects, where the declaration of any non-primitive always contains a
pointer to the memory where the object's data is stored, as opposed to
containing the acual values. That's why this doesn't compile:
class A {
int x;
}
A a1;
1 => a1.x;
A a2;
a1 => a2;
2 => a2.x;
<<< "a1.x=", a1.x, " a2.x=", a2.x>>>;
Hailstone:~ stefanblixt$ chuck test.ck
[test.ck]:line(8): cannot resolve operator '=>' on types 'A' and 'A'...
[test.ck]:line(8): ...(note: use '@=>' for object reference assignment)
After I've put the @ in there as the error message suggests, I get this
output from the program:
a1.x= 2 a2.x= 2
To create copies of an object you need to actively make a method in that
class that takes an instance as input, creates a completely new instance,
copies each member and then returns the new instance. There is no copy
constructor. See Java's
clonehttp://java.sun.com/javase/6/docs/api/java/lang/Object.html#clone()
method
in java.lang.Object.
Comparing with C/C++, there you can declare a struct/class variable that
isn't a pointer but rather contains all member data where it was declared,
allowing you to do crazy stuff like this:
#include
On 4 Oct 2009, at 02:21, Adam Tindale wrote:
This is a more direct version of what I meant to say. The problem I
encountered was that I was hoping to do some operations in a function and was hoping that I was passing a copy of an object, much like what happens with primitives. Since objects pass by reference then weird side effects and bugs can easily creep into a fellow ChucKist's code.
Yes, that is one side effect of it: side effects. The reference objects reference the same object on the heap, also when passed into a function.
Anyways, I hope this example is more clear.
--art
class test{ 0 => int val; }
test obj1; <<< obj1.val , "original object">>>;
fun void testclasspassing(test i) { 4 => i.val; <<< i.val, "in function" >>>; // side effect! }
testclasspassing(obj1); <<< obj1.val , "original object">>>;
So one way to think about it, is that this is the same as C/C++ void fun void testclasspassing(test* i) { 4 => i->val; <<< i->val, "in function" >>>; // side effect! } which in C++ can also be written as fun void testclasspassing(test& i) { 4 => i.val; <<< i.val, "in function" >>>; // side effect! } only that in chuck, the extra reference symbols are hidden away by the implementation of the reference object.
So when mutating the object within the function, as it is a reference to an object on the heap, it will be mutated there too.
By contrast, when one has a copy over type, a copy will be made, which will placed in the function stack. So when changing that within the function, that is a copy, and not the original object.
Hans
_______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
-- Release me, insect, or I will destroy the Cosmos!
On 4 Oct 2009, at 11:40, Stefan Blixt wrote:
I think you can pretty much say that ChucK follows Java's attitude towards objects, where the declaration of any non-primitive always contains a pointer to the memory where the object's data is stored, as opposed to containing the acual values.
I like to think on it the other way around: Everything in the computer must be stored somewhere, and so they are objects with addresses. But if the address can be ignored, say by a copy-over procedure, they become data or values. Or think of something physical, like a paper and what is written on it. The paper can't be copied itself, so it is an object. But if information on it can be copied over by some procedure, that becomes data.
That's why this doesn't compile:
class A { int x; }
A a1; 1 => a1.x; A a2; a1 => a2; 2 => a2.x; <<< "a1.x=", a1.x, " a2.x=", a2.x>>>;
Hailstone:~ stefanblixt$ chuck test.ck [test.ck]:line(8): cannot resolve operator '=>' on types 'A' and 'A'... [test.ck]:line(8): ...(note: use '@=>' for object reference assignment)
After I've put the @ in there as the error message suggests, I get this output from the program:
a1.x= 2 a2.x= 2
That is because a1 @=> a2; sets a2 to point at whatever a1 points at. I think Chuck could simplify and let => be applicable to objects where referencing is the default. Then for objects that support both copying and referencing, one might have spcial operators @=> and something for copying.
To create copies of an object you need to actively make a method in that class that takes an instance as input, creates a completely new instance, copies each member and then returns the new instance. There is no copy constructor. See Java's clone method in java.lang.Object.
One also distinguishes between a clone: making a new copy of the object pointed to, and deep clone: if the object and the subobjects contain references, make copies of them too
Comparing with C/C++, there you can declare a struct/class variable that isn't a pointer but rather contains all member data where it was declared, allowing you to do crazy stuff like this:
#include
struct { int x; } a1; struct { int x; } a2;
In C/C++, these are copy-over object: a compiler implementation defined chunk of memory that is copied over in full. Chuck implements for a class A something like (pseudocode) template<class A> ref<A> { A* p_ = 0; ... };
int main(char** argv) { a1.x = 1; a2.x = 3; *(&(a1.x) +4) = 2; // this may print "a1.x=1 a2.x=2" if the alignment is correct printf("a1.x=%d a2.x=%d\n", a1.x, a2.x); }
(Though the behavior is entirely compiler dependent. You can get around it by making an array and using sizeof() though, I think.)
The confusing bit in ChucK is that it allows declarations with or without a @ before the variable name. My interpretation of this is that
A a;
should be seen as a kind of shorthand for this:
new A @=> A @ a;
These two staments are identical, I think.
Yes. That seems to be the case. Hans
participants (5)
-
Adam Tindale
-
Hans Aberg
-
Kassen
-
Robert Poor
-
Stefan Blixt