digitalmars.D - Hiding class pointers -- was it a good idea?
- Bill Baxter (27/27) Aug 15 2007 I'm starting to seriously wonder if it was a good idea to hide the
- Jascha Wetzel (6/39) Aug 15 2007 i agree. D's rules for this are more complex than C++'s and their
- Deewiant (31/42) Aug 15 2007 I think the separation was a good idea. I like structs not having hidden...
- Bill Baxter (35/62) Aug 15 2007 Well, in C++ you only get those things if you declare one or more
- Deewiant (16/30) Aug 15 2007 You're right, explicit virtual helps. Classes could default to virtual, ...
- Bill Baxter (8/15) Aug 15 2007 ...unless storing as 'scope'. But anyway, I'm not sure what you mean:
- Deewiant (5/19) Aug 15 2007 In accordance with my "d'oh" above, yes, that's what I meant. ;-)
- Sean Kelly (6/9) Aug 15 2007 I'm not sure I agree. Stack allocation from the presence of "scope" is
- Tristam MacDonald (2/35) Aug 15 2007
- Gregor Richards (7/7) Aug 15 2007 #1 advantage of making them always by-value: Ridiculously inconsistent
- Bill Baxter (11/19) Aug 15 2007 I don't get your point. Each of those things has different uses in C++.
- Gregor Richards (8/34) Aug 15 2007 This just makes writing code more complicated. It's difficult to
- Regan Heath (15/49) Aug 15 2007 We have the same problem in D with value types like 'int'.
- Bill Baxter (12/46) Aug 15 2007 D functions can also take pointers, values or references, so I don't see...
- Walter Bright (2/4) Aug 15 2007 That is a deliberate positive feature, not an expense.
- Bill Baxter (37/42) Aug 15 2007 Ok. Thanks for all the responses.
- Leandro Lucarella (11/13) Aug 15 2007 I don't have slicing problem either, but I've seen a lot of code with th...
- Walter Bright (10/48) Aug 16 2007 The slicing problem comes about indirectly, not by directly trying to do...
- Walter Bright (3/4) Aug 15 2007 These different uses should be decided by the designer of the class, not...
- Gregor Richards (16/16) Aug 15 2007 OK, OK, I guess I should respond with an argument from computer science
- Walter Bright (7/25) Aug 15 2007 You put your finger on the very good reason why polymorphic, inheritable...
- James Dennett (11/37) Aug 15 2007 For one, rather restricted, notion of OOP. There are many,
- Walter Bright (12/22) Aug 16 2007 The definition I use is a common one, OOP design consists of three
- James Dennett (7/32) Aug 16 2007 Valid points, though removing expressive power tends to move
- Don Clugston (25/37) Aug 17 2007 And there are situations where they can even be true values, without
- Walter Bright (3/15) Aug 17 2007 The counterexample I'd give is that D classes tend to be implementable
- James Dennett (9/25) Aug 18 2007 One counterpoint to which is that most complexity in large
- Alex Burton (5/8) Aug 17 2007 Another one in that list should be "reference", which IMO would be assum...
- Tristam MacDonald (4/15) Aug 17 2007 Agreed, but I can't think of another language that supports this
- Alex Burton (17/24) Aug 17 2007 And I totally agree that mutable is a hack.
- James Dennett (8/27) Aug 18 2007 About every OO-aware language but D supports it; only D
- Reiner Pope (10/43) Aug 15 2007 How about allowing operator overloads on classes which must be treated
- Bill Baxter (13/25) Aug 15 2007 That's a good point. But it does kind of fall into the category of "no
- Russell Lewis (17/50) Aug 15 2007 I agree, IMHO this was a mistake. With automatic type deduction, the
- Tristam MacDonald (3/10) Aug 15 2007 I would tend to disagree. Classes are reference types, structs are value...
- Russell Lewis (26/36) Aug 15 2007 Tristam, your response felt a little "hotter" than I what I prefer. If
- Johan Granberg (17/19) Aug 15 2007 What about this situation.
- Walter Bright (16/41) Aug 15 2007 That's known as the 'slicing' problem. It's pernicious in that it can be...
- James Dennett (9/39) Aug 15 2007 It's trivially detected by various automated tools, which
- Robert Fraser (10/13) Aug 16 2007 Now that's just patently untrue. I've never done any serious work in C++...
- Walter Bright (2/8) Aug 16 2007 Right on.
- James Dennett (15/25) Aug 16 2007 It's true; one problem with C++ is that there is too much
- Walter Bright (3/15) Aug 16 2007 That's true if you agree with the notion that only abstract classes
- James Dennett (5/21) Aug 16 2007 Then you lose benefits of static type checking for operations
- Russell Lewis (10/13) Aug 17 2007 It frustrates me that people keep conflating what are orthogonal
- Bill Baxter (25/40) Aug 17 2007 And I assume you would still allow classes on the stack in that case.
- James Dennett (27/52) Aug 15 2007 // begin non-naive C++
- Sean Kelly (28/35) Aug 15 2007 Funny. I started out having concerns about the inconsistency here and
- Walter Bright (14/18) Aug 15 2007 Having classes be by reference only has some serious advantages:
- Brad Roberts (10/31) Aug 15 2007 They're _less_ relevant. There's still the valid usecase of acting like...
- Walter Bright (9/43) Aug 15 2007 I strongly feel that for that usecase, one should be using a value type,...
- Bill Baxter (15/56) Aug 15 2007 The problem I see is that it's really not always clear whether a value
- Walter Bright (5/18) Aug 16 2007 If you're using interfaces in a class, it's consuming 8 bytes plus 4
- James Dennett (11/31) Aug 15 2007 Trivial in C++: entity types are declared as "noncopyable", base
- Bill Baxter (8/45) Aug 16 2007 Yes, that's pretty much the same conclusion I came to. C++'s way gives
- Walter Bright (8/14) Aug 16 2007 That isn't my experience. In my work on DMDscript in D, I changed some
- James Dennett (24/39) Aug 16 2007 In C++, no, you didn't, as structs and classes are no different
- Walter Bright (2/17) Aug 17 2007 I meant I switched between value and reference semantics for an object.
- James Dennett (9/32) Aug 18 2007 Careful with selective quoting! I recognized that you
- eao197 (21/39) Aug 16 2007 From my expirience this is a problem of C++ beginners.
- Bill Baxter (5/27) Aug 16 2007 I hear you. Fortunately it's pretty trivial to throw some
- eao197 (9/28) Aug 16 2007 I suppose that the problem becomes more complex in the case when templat...
- Matthias Walter (9/39) Aug 16 2007 There's an example going to the reverse direction:
- Walter Bright (16/17) Aug 16 2007 I was talking to a person who is a C++ developer for a major game
- eao197 (25/41) Aug 16 2007 I know.
- eao197 (7/10) Aug 16 2007 Oops! Typo :((
- Walter Bright (3/23) Aug 16 2007 But I think if one pulls on that string, one eventually winds up with
- Stephen Waits (9/14) Aug 16 2007 I can second this.
- Walter Bright (9/25) Aug 16 2007 Right. One of the goals of D is to enable regular programmers to get
- kris (2/25) Aug 16 2007 amen
- James Dennett (12/31) Aug 16 2007 An important observation. And given that programmers starting
- Jason House (13/26) Aug 15 2007 The simple distinction between reference types and value types has irked...
- Bill Baxter (32/66) Aug 15 2007 Well realistically none of this is likely to change anyway, so you're
- Jason House (12/42) Aug 16 2007 I was thinking of something more consistent.
- Bill Baxter (15/54) Aug 16 2007 Oh, I see. So you mean that no matter what, the compiler would figure
- Russell Lewis (3/78) Aug 17 2007 It also doesn't help clarity (completely) because you can still have
I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability. Bad points: - Harder to tell whether you're dealing with a pointer or not (c.f. the common uninitialized 'MyObject obj;' bug) - To build on the stack, have to use 'scope' Good points: + No need to type '*' everywhere when you use class objects + ??? anything else ??? I guess I feel like that if D were Java, I'd never see the pointers, "Foo foo;" would always mean a heap-allocated thing, and everything would be cool. But with D sometimes "Foo foo" means a stack allocated thing, and sometimes it doesn't. I'm starting to think the extra cognitive load isn't really worth it. In large part it all stems from the decision to make classes and structs different. That seems nice in theory, but as time goes on the two are becoming more and more alike. Classes can now be stack-allocated with scope. Structs are supposedly going to get constructors, and maybe even destructors. It kind of makes me think that maybe separating structs and classes was a bad idea. I have yet to ever once say to myself "thank goodness structs and classes are different in D!", though plenty of times I've muttered "dangit why can't I just get a default copy constructor for this class" or "shoot, these static opCalls are annoying" or "dang, I wish I could get a weak reference to a struct", etc. --bb
Aug 15 2007
Bill Baxter wrote:I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability. Bad points: - Harder to tell whether you're dealing with a pointer or not (c.f. the common uninitialized 'MyObject obj;' bug) - To build on the stack, have to use 'scope' Good points: + No need to type '*' everywhere when you use class objects + ??? anything else ??? I guess I feel like that if D were Java, I'd never see the pointers, "Foo foo;" would always mean a heap-allocated thing, and everything would be cool. But with D sometimes "Foo foo" means a stack allocated thing, and sometimes it doesn't. I'm starting to think the extra cognitive load isn't really worth it. In large part it all stems from the decision to make classes and structs different. That seems nice in theory, but as time goes on the two are becoming more and more alike. Classes can now be stack-allocated with scope. Structs are supposedly going to get constructors, and maybe even destructors. It kind of makes me think that maybe separating structs and classes was a bad idea. I have yet to ever once say to myself "thank goodness structs and classes are different in D!", though plenty of times I've muttered "dangit why can't I just get a default copy constructor for this class" or "shoot, these static opCalls are annoying" or "dang, I wish I could get a weak reference to a struct", etc. --bbi agree. D's rules for this are more complex than C++'s and their advantage is questionable. besides, not having to type *'s (everywhere except in uninitialized declarations) is a separate feature. we could have C++-like behaviour and still use the implicit * deduction.
Aug 15 2007
Bill Baxter wrote:In large part it all stems from the decision to make classes and structs different. That seems nice in theory, but as time goes on the two are becoming more and more alike. Classes can now be stack-allocated with scope. Structs are supposedly going to get constructors, and maybe even destructors. It kind of makes me think that maybe separating structs and classes was a bad idea. I have yet to ever once say to myself "thank goodness structs and classes are different in D!", though plenty of times I've muttered "dangit why can't I just get a default copy constructor for this class" or "shoot, these static opCalls are annoying" or "dang, I wish I could get a weak reference to a struct", etc.I think the separation was a good idea. I like structs not having hidden fields, being laid out in memory exactly as I write the code, and having no performance overhead due to vtables, etc. In C++, the only difference is that structs default to public while classes default to private. Where's the use in that? Just use a class for everything - and most people do. I like my POD datatype, thank you very much. With that said, I agree that hiding class pointers may not have been a good idea. Still, I can't think of many points which would make it a particularly bad idea. Defaulting to stack allocation somehow would be better: it's easy to write "auto" instead of "scope" and forget about it. But then, the performance in such cases rarely matters, and when it does, you do notice such things because you're looking for them. "Myobject obj;" being uninitialized is supposed to be a feature, not a bug, just like "T t;" should be explicitly initialized for every T. The idea is that if you forget to initialize something you get an easy-to-find error: all chars are initialized to 0xff, so if you're wondering why you're getting running into lots of 0xff in some output, you look for uninitialized chars. Ditto for floating point and NaNs. Integers don't have an "uninitialized" value so they go to zero. IMHO this should perhaps have been made T.min or T.max instead of zero, as zero is so common an initializer that people forget the reason why variables are initialized by default. Walter said that even he's guilty of sometimes leaving integers uninitialized when they should be initialized to zero, and I do it too, but that doesn't mean that's how it should be done. Initializers are present to catch errors by making code fail hard (as in the "Object o;" case) instead of silently (as in if you type "int x;" in C/C++ and proceed to increment or decrement x). -- Remove ".doesnotlike.spam" from the mail address.
Aug 15 2007
Deewiant wrote:Bill Baxter wrote:Well, in C++ you only get those things if you declare one or more functions as virtual. D automates flagging functions with 'virtual', so you couldn't do the same thing in D. But you could imagine a D with a 'nonvirtual' keyword that if stuck into a class would make it POD.In large part it all stems from the decision to make classes and structs different. That seems nice in theory, but as time goes on the two are becoming more and more alike. Classes can now be stack-allocated with scope. Structs are supposedly going to get constructors, and maybe even destructors. It kind of makes me think that maybe separating structs and classes was a bad idea. I have yet to ever once say to myself "thank goodness structs and classes are different in D!", though plenty of times I've muttered "dangit why can't I just get a default copy constructor for this class" or "shoot, these static opCalls are annoying" or "dang, I wish I could get a weak reference to a struct", etc.I think the separation was a good idea. I like structs not having hidden fields, being laid out in memory exactly as I write the code, and having no performance overhead due to vtables, etc.In C++, the only difference is that structs default to public while classes default to private. Where's the use in that?Very little point in it, I agree. C++ could have just stuck with 'struct' and been just fine. Part of my point was that maybe we didn't really need two different things in D either.Just use a class for everything - and most people do. I like my POD datatype, thank you very much.I like my POD datatypes too. And I like the fact that in C++ I can change from POD to not-POD by adding one little word to the class/struct ("virtual"). Almost all code that relies on that POD type will still work if it gains a vtable and one or more virtual methods. In D it's much more difficult. static opCalls need to be changed to this() constructors, and every bit of code that uses the thing will have to be changed -- mostly Foo*'s will need to be changed to Foo's. But some places may be better off changing to scope Foo. So you need to look closely at the code you're changing.With that said, I agree that hiding class pointers may not have been a good idea. Still, I can't think of many points which would make it a particularly bad idea.I guess the main down side is just the inconsistency of value/ptr usages -- Foo/Foo* on the one hand and scope Foo/Foo on the other. It's easy to get used to lack of pointers in Java because it's consistent. But in D you have to go back and forth between C-like and Java-like line-by-line in your code. Put it this way, if I download someone's container library, I don't want to have to think about whether their CoolSet is a class or a struct in order to create one by value. Note that built-in containers are basically struct-like, so in some ways it makes sense for user defined containers to be structs, too. And C++ STL containers are almost always used by-value (i.e. you almost never see "new std::vector()"). But on the other hand a container may want to use interfaces or inheritance in the implementation, and writing static opCalls is just a pain, so it also also makes sense to use a class. The fact that it could reasonably be either class or struct means there's one more bit of information I have to keep in mind for every user-defined type in my code.Defaulting to stack allocation somehow would be better: it's easy to write "auto" instead of "scope" and forget about it. But then, the performance in such cases rarely matters, and when it does, you do notice such things because you're looking for them.I don't think the performance is as much of an issue as consistency. --bb
Aug 15 2007
Bill Baxter wrote:I like my POD datatypes too. And I like the fact that in C++ I can change from POD to not-POD by adding one little word to the class/struct ("virtual"). Almost all code that relies on that POD type will still work if it gains a vtable and one or more virtual methods. In D it's much more difficult. static opCalls need to be changed to this() constructors, and every bit of code that uses the thing will have to be changed -- mostly Foo*'s will need to be changed to Foo's. But some places may be better off changing to scope Foo. So you need to look closely at the code you're changing.You're right, explicit virtual helps. Classes could default to virtual, and structs to non-virtual. Now _that_ would be handy. Newbies or people who don't care about this low-level stuff could just use classes for everything as it is now, but struct use would be simpler.Put it this way, if I download someone's container library, I don't want to have to think about whether their CoolSet is a class or a struct in order to create one by value.This I agree with. Even type inference doesn't solve this problem. What I like about C++ is that "new" means "allocate on the heap". In D, you have to add "unless storing as 'scope'".I don't think the performance is as much of an issue as consistency.I don't see the consistency problem between stack and heap allocation. In fact, upon further reflection, what heap allocation has going for it is that it's more consistent: you can do "Obj o = new Obj" followed by a "return o", and it works, just as you can do "int x = 5" followed by "return x". If stack allocation were the default, "return o" would be a problem, because it's a pointer to the now-invalid stack. -- Remove ".doesnotlike.spam" from the mail address.
Aug 15 2007
Deewiant wrote:I don't see the consistency problem between stack and heap allocation. In fact, upon further reflection, what heap allocation has going for it is that it's more consistent: you can do "Obj o = new Obj" followed by a "return o", and it works, just as you can do "int x = 5" followed by "return x". If stack allocation were the default, "return o" would be a problem, because it's a pointer to the now-invalid stack....unless storing as 'scope'. But anyway, I'm not sure what you mean: if by-value were the default, then 'return o' would return a copy of 'o' not a pointer to it. And even if it did return a pointer to it, it's no different than trying to return a pointer to a scope class, or to any other function-local data. So maybe you mean "more safe" rather than "more consistent"? --bb
Aug 15 2007
Bill Baxter wrote:Deewiant wrote:D'oh! Good point.In fact, upon further reflection, what heap allocation has going for it is that it's more consistent: you can do "Obj o = new Obj" followed by a "return o", and it works, just as you can do "int x = 5" followed by "return x". If stack allocation were the default, "return o" would be a problem, because it's a pointer to the now-invalid stack....unless storing as 'scope'. But anyway, I'm not sure what you mean: if by-value were the default, then 'return o' would return a copy of 'o' not a pointer to it.And even if it did return a pointer to it, it's no different than trying to return a pointer to a scope class, or to any other function-local data. So maybe you mean "more safe" rather than "more consistent"?In accordance with my "d'oh" above, yes, that's what I meant. ;-) -- Remove ".doesnotlike.spam" from the mail address.
Aug 15 2007
Deewiant wrote:What I like about C++ is that "new" means "allocate on the heap". In D, you have to add "unless storing as 'scope'".I'm not sure I agree. Stack allocation from the presence of "scope" is a QOI issue, it's not guaranteed in the spec. All "scope" really says is "destroy what this reference refers to when the reference goes out of scope." Sean
Aug 15 2007
Possibly because they are not 'pointers to classes' in a strict sense. They don't necessarily have to be implemented with a pointer (though this will usually be the case), and they don't support many *unsafe* pointer operations, such as initialisation to anything (only to objects or null), or arithmetic. Bill Baxter Wrote:I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability. Bad points: - Harder to tell whether you're dealing with a pointer or not (c.f. the common uninitialized 'MyObject obj;' bug) - To build on the stack, have to use 'scope' Good points: + No need to type '*' everywhere when you use class objects + ??? anything else ??? I guess I feel like that if D were Java, I'd never see the pointers, "Foo foo;" would always mean a heap-allocated thing, and everything would be cool. But with D sometimes "Foo foo" means a stack allocated thing, and sometimes it doesn't. I'm starting to think the extra cognitive load isn't really worth it. In large part it all stems from the decision to make classes and structs different. That seems nice in theory, but as time goes on the two are becoming more and more alike. Classes can now be stack-allocated with scope. Structs are supposedly going to get constructors, and maybe even destructors. It kind of makes me think that maybe separating structs and classes was a bad idea. I have yet to ever once say to myself "thank goodness structs and classes are different in D!", though plenty of times I've muttered "dangit why can't I just get a default copy constructor for this class" or "shoot, these static opCalls are annoying" or "dang, I wish I could get a weak reference to a struct", etc. --bb
Aug 15 2007
use of by-value passing, ref passing and pointer passing. void foo(Thing a); void foo(ref Thing a); void foo(Thing *a); Wait, that's not an advantage at all, that's C++. - Gregor Richards
Aug 15 2007
Gregor Richards wrote:use of by-value passing, ref passing and pointer passing. void foo(Thing a); void foo(ref Thing a); void foo(Thing *a); Wait, that's not an advantage at all, that's C++.I don't get your point. Each of those things has different uses in C++. // if Thing is small/PoD and you don't want changes to affect caller void foo(Thing a); // if Thing is bigger, you want to allow caller-visible changes, and you require calling with non-null void foo(ref Thing a); // same as above, but you want to allow null too void foo(Thing *a); The ref-can't-be-null thing may not hold for D, but it's true of C++. --bb
Aug 15 2007
Bill Baxter wrote:Gregor Richards wrote:This just makes writing code more complicated. It's difficult to remember whether some function wants a reference or a pointer, and you have to be careful about how you treat them because they're fundamentally different (see the operator overloading post). The advantage is virtually nil, and it would create this huge complication. That's why it's terrible in C++, and that's why it would be terrible in D. - Gregor Richardsuse of by-value passing, ref passing and pointer passing. void foo(Thing a); void foo(ref Thing a); void foo(Thing *a); Wait, that's not an advantage at all, that's C++.I don't get your point. Each of those things has different uses in C++. // if Thing is small/PoD and you don't want changes to affect caller void foo(Thing a); // if Thing is bigger, you want to allow caller-visible changes, and you require calling with non-null void foo(ref Thing a); // same as above, but you want to allow null too void foo(Thing *a); The ref-can't-be-null thing may not hold for D, but it's true of C++. --bb
Aug 15 2007
Gregor Richards wrote:Bill Baxter wrote:We have the same problem in D with value types like 'int'. Pass by reference has always bothered me. It hides what's going on at the call site, eg. int a; foo(a); bar(a); which one modifies a? Then there is the fact that you cannot pass null, meaning in some cases creating and passing a dummy parameter for an 'out' which you don't actually want. I actually quite like pointers myself but I like D's arrays more! They're passed by value or by reference and you can always pass null. If only classes could work in the same way! ReganGregor Richards wrote:This just makes writing code more complicated. It's difficult to remember whether some function wants a reference or a pointer, and you have to be careful about how you treat them because they're fundamentally different (see the operator overloading post). The advantage is virtually nil, and it would create this huge complication. That's why it's terrible in C++, and that's why it would be terrible in D.inconsistent use of by-value passing, ref passing and pointer passing. void foo(Thing a); void foo(ref Thing a); void foo(Thing *a); Wait, that's not an advantage at all, that's C++.I don't get your point. Each of those things has different uses in C++. // if Thing is small/PoD and you don't want changes to affect caller void foo(Thing a); // if Thing is bigger, you want to allow caller-visible changes, and you require calling with non-null void foo(ref Thing a); // same as above, but you want to allow null too void foo(Thing *a); The ref-can't-be-null thing may not hold for D, but it's true of C++. --bb
Aug 15 2007
Gregor Richards wrote:Bill Baxter wrote:D functions can also take pointers, values or references, so I don't see that D is really that different in this respect. I guess you mean that, *if* you're talking about a class type, then you can be pretty certain you won't need to call a function using a derefernce like foo(&theThing). Yeh, I guess that is a positive point of hiding pointers. Of course it comes at the expense of making it impossible to pass a class by value. You could also just outlaw pass-by-value for classes, and automatically dereference any value classes passed to functions expecting a class pointer. So I don't think it's strictly necessary to hide the pointer in order to get this benefit you speak of. --bbGregor Richards wrote:This just makes writing code more complicated. It's difficult to remember whether some function wants a reference or a pointer, and you have to be careful about how you treat them because they're fundamentally different (see the operator overloading post). The advantage is virtually nil, and it would create this huge complication. That's why it's terrible in C++, and that's why it would be terrible in D.inconsistent use of by-value passing, ref passing and pointer passing. void foo(Thing a); void foo(ref Thing a); void foo(Thing *a); Wait, that's not an advantage at all, that's C++.I don't get your point. Each of those things has different uses in C++. // if Thing is small/PoD and you don't want changes to affect caller void foo(Thing a); // if Thing is bigger, you want to allow caller-visible changes, and you require calling with non-null void foo(ref Thing a); // same as above, but you want to allow null too void foo(Thing *a); The ref-can't-be-null thing may not hold for D, but it's true of C++. --bb
Aug 15 2007
Bill Baxter wrote:Of course it comes at the expense of making it impossible to pass a class by value.That is a deliberate positive feature, not an expense.
Aug 15 2007
Walter Bright wrote:Bill Baxter wrote:Ok. Thanks for all the responses. About slicing: it may be totally evil and impossible to debug when it occurs, but I have to say I can't recall ever being bitten by it in 10 years of C++ programming. But I tend not to go crazy with the polymorphism in the first place, and if I do I guess I've been trained to avoid passing by-value classes around. Just thinking about it triggers my "danger" reflex. Always makes me queasy when I see things like std::string being passed around by value even though I know that most implementations are optimized to avoid copying the payload. And I certainly would never try to assign different by-value classes to one another. So yeh, you've eliminated slicing, but I'm not really convinced it was such a huge problem that it warranted a syntax upheaval in the first place. But I do understand the logic that if you want polymorphic behavior you _have_ to call via a reference or pointer. And if you *always* have to call via reference/pointer, then you might as well make that the default. I think when it comes down to it the only things I'm really going to have left to complain about are missing features in structs: 1) the lack of constructors for structs (going to be fixed), 2) lack of (non-polymorphic) inheritance for structs (not so major), 3) lack of const reference for convenient passing of structs around (from what I understand the current situation in D 2.0 is that the closest you can come is a const pointer to a struct, meaning that every time I call the function I have to remember to dereference the args: rotate(&quat,&vec). Bleh.) Initially I unrealistically thought D's class/struct thing was going to be some kind of super magic bullet, recently it dawned on me that it wasn't panning out that way (hence these posts), and finally I guess I'm coming to the conclusion (thanks for all the input!) that D's class/struct thing is not worse than C++, but isn't necessarily better than C++'s way, either. They both have issues, just the issues are shifted around. And maybe I'll eventually come to view D's way as slightly better -- esp. if the 3 issues above are sorted out ;-). But I've been permanently disabused of my illusions that separating class/struct is some sort of magic cure-all. :-) --bbOf course it comes at the expense of making it impossible to pass a class by value.That is a deliberate positive feature, not an expense.
Aug 15 2007
Bill Baxter, el 16 de agosto a las 10:07 me escribiste:So yeh, you've eliminated slicing, but I'm not really convinced it was such a huge problem that it warranted a syntax upheaval in the first place.I don't have slicing problem either, but I've seen a lot of code with that problem from novice (and not so novice) programmers, and it was really hard to debug. A place where is fairly common is on exception handling. -- Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/ .------------------------------------------------------------------------, \ GPG: 5F5A8D05 // F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05 / '--------------------------------------------------------------------' El amor es como una reina ortopédica. -- Poroto
Aug 15 2007
Bill Baxter wrote:About slicing: it may be totally evil and impossible to debug when it occurs, but I have to say I can't recall ever being bitten by it in 10 years of C++ programming. But I tend not to go crazy with the polymorphism in the first place, and if I do I guess I've been trained to avoid passing by-value classes around. Just thinking about it triggers my "danger" reflex. Always makes me queasy when I see things like std::string being passed around by value even though I know that most implementations are optimized to avoid copying the payload. And I certainly would never try to assign different by-value classes to one another.The slicing problem comes about indirectly, not by directly trying to do such assignments.So yeh, you've eliminated slicing, but I'm not really convinced it was such a huge problem that it warranted a syntax upheaval in the first place.It's a well-known problem with C++, inspiring many conventions and idioms to avoid. I haven't myself been bitten by it either - but one of D's aims is to be a robust, reliable and auditable language. Trying to avoid such gaping holes in the type system is very important. Relying on convention simply is not good enough.But I do understand the logic that if you want polymorphic behavior you _have_ to call via a reference or pointer. And if you *always* have to call via reference/pointer, then you might as well make that the default. I think when it comes down to it the only things I'm really going to have left to complain about are missing features in structs: 1) the lack of constructors for structs (going to be fixed), 2) lack of (non-polymorphic) inheritance for structs (not so major), 3) lack of const reference for convenient passing of structs around (from what I understand the current situation in D 2.0 is that the closest you can come is a const pointer to a struct, meaning that every time I call the function I have to remember to dereference the args: rotate(&quat,&vec). Bleh.) Initially I unrealistically thought D's class/struct thing was going to be some kind of super magic bullet, recently it dawned on me that it wasn't panning out that way (hence these posts), and finally I guess I'm coming to the conclusion (thanks for all the input!) that D's class/struct thing is not worse than C++, but isn't necessarily better than C++'s way, either. They both have issues, just the issues are shifted around. And maybe I'll eventually come to view D's way as slightly better -- esp. if the 3 issues above are sorted out ;-). But I've been permanently disabused of my illusions that separating class/struct is some sort of magic cure-all. :-)There are plans to deal with (1) and (3), and you can do (2) using aggregation instead of inheritance.
Aug 16 2007
Bill Baxter wrote:Each of those things has different uses in C++.These different uses should be decided by the designer of the class, not the user of the class.
Aug 15 2007
OK, OK, I guess I should respond with an argument from computer science as well :) In the normal definition of Object Orientation, an object is a means of storing a context in which operations can be performed. The abstraction behind this (yay we're modeling the universe in code but realistically we aren't yay) is irrelevant, as fundamentally OO is just a means of storing and passing contexts. Because this is a context, it makes no sense whatsoever to pass it around with duplication - duplicating contexts is nonsense. structs are sort of a hack for compatibility and/or optimization. They are not contexts, they are means of creating more complicated values. While a "Point" could be a struct, really being a more complicated value, an "NPC" would always be a class, since it is a context. The fact that this is inconsistent with C++ is irrelevant: D is more to the spirit of good OO. - Gregor Richards
Aug 15 2007
Gregor Richards wrote:OK, OK, I guess I should respond with an argument from computer science as well :) In the normal definition of Object Orientation, an object is a means of storing a context in which operations can be performed. The abstraction behind this (yay we're modeling the universe in code but realistically we aren't yay) is irrelevant, as fundamentally OO is just a means of storing and passing contexts. Because this is a context, it makes no sense whatsoever to pass it around with duplication - duplicating contexts is nonsense. structs are sort of a hack for compatibility and/or optimization. They are not contexts, they are means of creating more complicated values. While a "Point" could be a struct, really being a more complicated value, an "NPC" would always be a class, since it is a context. The fact that this is inconsistent with C++ is irrelevant: D is more to the spirit of good OO.You put your finger on the very good reason why polymorphic, inheritable types in D are restricted to being reference types, not value types. OOP requires this characteristic. In C++, an OOP class can be used/misused by the user as a value type or a reference type, all out of the purview of the class designer. The class designer must control this, not the class user.
Aug 15 2007
Walter Bright wrote:Gregor Richards wrote:For one, rather restricted, notion of OOP. There are many, many views of what constitutes OOP in the PL community.OK, OK, I guess I should respond with an argument from computer science as well :) In the normal definition of Object Orientation, an object is a means of storing a context in which operations can be performed. The abstraction behind this (yay we're modeling the universe in code but realistically we aren't yay) is irrelevant, as fundamentally OO is just a means of storing and passing contexts. Because this is a context, it makes no sense whatsoever to pass it around with duplication - duplicating contexts is nonsense. structs are sort of a hack for compatibility and/or optimization. They are not contexts, they are means of creating more complicated values. While a "Point" could be a struct, really being a more complicated value, an "NPC" would always be a class, since it is a context. The fact that this is inconsistent with C++ is irrelevant: D is more to the spirit of good OO.You put your finger on the very good reason why polymorphic, inheritable types in D are restricted to being reference types, not value types. OOP requires this characteristic.In C++, an OOP class can be used/misused by the user as a value type or a reference type, all out of the purview of the class designer. The class designer must control this, not the class user.It's normal in C++ to make "entity" classes (those that you're calling reference types) noncopyable. It's also normal to make base classes abstract. Thus idioms easily prevent the basic misuses. I can't think of any reason why a value type would object to the identity of its objects being used (though functional languages often hide object identities). -- James
Aug 15 2007
James Dennett wrote:For one, rather restricted, notion of OOP. There are many, many views of what constitutes OOP in the PL community.The definition I use is a common one, OOP design consists of three characteristics: 1) inheritance 2) polymorphism 3) encapsulationC++ is loaded with idioms and conventions to try and head off major problems. I'd rather snip off such problems at the source - for one reason, it will dramatically reduce the learning curve of the language. For another, the more guarantees the language can offer, the lesser a burden it is on the code auditor, and the more likely the code is to be correct.In C++, an OOP class can be used/misused by the user as a value type or a reference type, all out of the purview of the class designer. The class designer must control this, not the class user.It's normal in C++ to make "entity" classes (those that you're calling reference types) noncopyable. It's also normal to make base classes abstract. Thus idioms easily prevent the basic misuses.
Aug 16 2007
Walter Bright wrote:James Dennett wrote:Then objects are in no way disallowed from having value semantics.For one, rather restricted, notion of OOP. There are many, many views of what constitutes OOP in the PL community.The definition I use is a common one, OOP design consists of three characteristics: 1) inheritance 2) polymorphism 3) encapsulationValid points, though removing expressive power tends to move the complexity into code, and it's hard to remove the power to write bad code without also removing the power to write great code. -- JamesC++ is loaded with idioms and conventions to try and head off major problems. I'd rather snip off such problems at the source - for one reason, it will dramatically reduce the learning curve of the language. For another, the more guarantees the language can offer, the lesser a burden it is on the code auditor, and the more likely the code is to be correct.In C++, an OOP class can be used/misused by the user as a value type or a reference type, all out of the purview of the class designer. The class designer must control this, not the class user.It's normal in C++ to make "entity" classes (those that you're calling reference types) noncopyable. It's also normal to make base classes abstract. Thus idioms easily prevent the basic misuses.
Aug 16 2007
James Dennett wrote:Walter Bright wrote:And there are situations where they can even be true values, without encountering slicing problems. It always irked me that in C++ there's no natural way of having polymorphic value types. (And D hasn't changed this). The case where you have class Base { int a_; int b_; public: virtual int func() { ... } // use a_ and b_ }; class Derived : public Base { virtual int func() { ... } }; And the interesting thing about Derived is that it only adds an interface, not data. It's kind of like a abstract base class, in reverse. An instance of Derived is just a Base, but with a different value in the hidden vtable parameter. If a derived class does not add any data members (or multiple inheritance), then there's never a slicing problem. You can have a container of polymorphic classes, stored as values, with perfect safety. I this is a pretty common scenario, but there's no language support for it. Would be great to be able to say 'any class derived from this class is not permitted to add data' -- abstract derivations only.James Dennett wrote:Then objects are in no way disallowed from having value semantics.For one, rather restricted, notion of OOP. There are many, many views of what constitutes OOP in the PL community.The definition I use is a common one, OOP design consists of three characteristics: 1) inheritance 2) polymorphism 3) encapsulation
Aug 17 2007
James Dennett wrote:Walter Bright wrote:The counterexample I'd give is that D classes tend to be implementable with much less complex code than C++ classes.C++ is loaded with idioms and conventions to try and head off major problems. I'd rather snip off such problems at the source - for one reason, it will dramatically reduce the learning curve of the language. For another, the more guarantees the language can offer, the lesser a burden it is on the code auditor, and the more likely the code is to be correct.Valid points, though removing expressive power tends to move the complexity into code, and it's hard to remove the power to write bad code without also removing the power to write great code.
Aug 17 2007
Walter Bright wrote:James Dennett wrote:One counterpoint to which is that most complexity in large systems arises at larger scales than individual classes, and then a language that allows you to express more of the properties of a class tends to fare better "in the large". But, as is sadly usual for our field, it's hard to provide robust objective data to support either viewpoint, and we're down to debating based on our (widely varying) experiences. -- JamesWalter Bright wrote:The counterexample I'd give is that D classes tend to be implementable with much less complex code than C++ classes.C++ is loaded with idioms and conventions to try and head off major problems. I'd rather snip off such problems at the source - for one reason, it will dramatically reduce the learning curve of the language. For another, the more guarantees the language can offer, the lesser a burden it is on the code auditor, and the more likely the code is to be correct.Valid points, though removing expressive power tends to move the complexity into code, and it's hard to remove the power to write bad code without also removing the power to write great code.
Aug 18 2007
Walter Bright Wrote:1) inheritance 2) polymorphism 3) encapsulationAnother one in that list should be "reference", which IMO would be assumed by most people, but has been lost with transitive const in D. I would define it as the ability to not encapsulate something a class has a reference to. By making all objects that an object has references to necessarily part of that object, the transitive const feature means that the only state relationship a class can have to another class is encapsulation. Correct me if i'm wrong :)
Aug 17 2007
Agreed, but I can't think of another language that supports this definition of OOP (apart from 'mutable' under C++ - which is widely regarded as a hack). Alex Burton wrote:Walter Bright Wrote:1) inheritance 2) polymorphism 3) encapsulationAnother one in that list should be "reference", which IMO would be assumed by most people, but has been lost with transitive const in D. I would define it as the ability to not encapsulate something a class has a reference to. By making all objects that an object has references to necessarily part of that object, the transitive const feature means that the only state relationship a class can have to another class is encapsulation. Correct me if i'm wrong :)
Aug 17 2007
Tristam MacDonald Wrote:Agreed, but I can't think of another language that supports this definition of OOP (apart from 'mutable' under C++ - which is widely regarded as a hack). Alex Burton wrote:And I totally agree that mutable is a hack. But when a class has a *pointer* to another object in C++, I can choose whether to tell the compiler that the other object is part of the class or not. In D all classes are referred to by pointers. And all pointers are assumed to mean part of (because of transitive const). It is perfectly logical to disallow changing objects that are part of a const class. I am having trouble figuring out how I am going to write code when I can't store a pointer to something in a class without it being interpreted as a part of relationship. Clearly in the below code transitive const does not make sense. And does not allow me to describe reality. Which is what I need to do to write sensible code. class Table { Database mDatabase; const const(Data) getData() { mDatabase.lock(); //error cannot change the state of mDatabase even though it can be in no way thought of as part of Table ... mDatabase.unlock(); } };By making all objects that an object has references to necessarily part of that object, the transitive const feature means that the only state relationship a class can have to another class is encapsulation.
Aug 17 2007
Tristam MacDonald wrote:Alex Burton wrote:About every OO-aware language but D supports it; only D (2.0) assumes in its type system that reference to another object always implies a whole-part relationship between them. "mutable" isn't much related to this (and note that widely held opinions are often not shared with informed experts). -- JamesWalter Bright Wrote:Agreed, but I can't think of another language that supports this definition of OOP (apart from 'mutable' under C++ - which is widely regarded as a hack).1) inheritance 2) polymorphism 3) encapsulationAnother one in that list should be "reference", which IMO would be assumed by most people, but has been lost with transitive const in D. I would define it as the ability to not encapsulate something a class has a reference to. By making all objects that an object has references to necessarily part of that object, the transitive const feature means that the only state relationship a class can have to another class is encapsulation. Correct me if i'm wrong :)
Aug 18 2007
Bill Baxter wrote:I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability. Bad points: - Harder to tell whether you're dealing with a pointer or not (c.f. the common uninitialized 'MyObject obj;' bug) - To build on the stack, have to use 'scope' Good points: + No need to type '*' everywhere when you use class objects + ??? anything else ??? I guess I feel like that if D were Java, I'd never see the pointers, "Foo foo;" would always mean a heap-allocated thing, and everything would be cool. But with D sometimes "Foo foo" means a stack allocated thing, and sometimes it doesn't. I'm starting to think the extra cognitive load isn't really worth it. In large part it all stems from the decision to make classes and structs different. That seems nice in theory, but as time goes on the two are becoming more and more alike. Classes can now be stack-allocated with scope. Structs are supposedly going to get constructors, and maybe even destructors. It kind of makes me think that maybe separating structs and classes was a bad idea. I have yet to ever once say to myself "thank goodness structs and classes are different in D!", though plenty of times I've muttered "dangit why can't I just get a default copy constructor for this class" or "shoot, these static opCalls are annoying" or "dang, I wish I could get a weak reference to a struct", etc. --bbHow about allowing operator overloads on classes which must be treated as reference types? ByValue* a; // can't overload opAdd usefully: assert(a.opAdd(5) != a + 5); ByRef b; // but we can if the pointer is hidden assert(b.opAdd(5) == a + 5); -- Reiner
Aug 15 2007
Reiner Pope wrote:Bill Baxter wrote: How about allowing operator overloads on classes which must be treated as reference types? ByValue* a; // can't overload opAdd usefully: assert(a.opAdd(5) != a + 5); ByRef b; // but we can if the pointer is hidden assert(b.opAdd(5) == a + 5);That's a good point. But it does kind of fall into the category of "no need to type '*' everywhere". You could just say *a + 5. Or in C++ you could use a reference: ByValue&. But yeh, you pretty much need real reference types like in C++ if you don't hide the pointer. Another alternative might be to make a distinction between traditional raw memory pointers and pointers to objects. Then a+5 could automatically dereference 'a' if it's an object pointer. And if you want to do pointer arithmetic you would need some special syntax. But now that I think about it, that 'object pointer' is really just a reference type by another name. It's a pointer that acts like a value by automatically dereferencing when needed. --bb
Aug 15 2007
I agree, IMHO this was a mistake. With automatic type deduction, the lack of '*' doesn't really save any typing anymore, at least for declarations, which (I suspect) are by far the most common use: auto temp = new MyClass; It saves (a tiny bit of) typing on function declarations, and avoids the need for a copy constructor, but we could have eliminated copy constructors simply by making pass-by-value class arguments to functions illegal. But the worst of it all, IMHO, is what you (and others) have pointed out: the lack of clarity about whether a declaration is a struct-by-value or class-by-reference. That means that if a container was originally implemented as a struct, you can't ever change it to a class, since that would require rewriting all of the code that uses it. Doesn't that violate one of the basic principles of Object Orientation - the hiding of implementation? Russ Bill Baxter wrote:I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability. Bad points: - Harder to tell whether you're dealing with a pointer or not (c.f. the common uninitialized 'MyObject obj;' bug) - To build on the stack, have to use 'scope' Good points: + No need to type '*' everywhere when you use class objects + ??? anything else ??? I guess I feel like that if D were Java, I'd never see the pointers, "Foo foo;" would always mean a heap-allocated thing, and everything would be cool. But with D sometimes "Foo foo" means a stack allocated thing, and sometimes it doesn't. I'm starting to think the extra cognitive load isn't really worth it. In large part it all stems from the decision to make classes and structs different. That seems nice in theory, but as time goes on the two are becoming more and more alike. Classes can now be stack-allocated with scope. Structs are supposedly going to get constructors, and maybe even destructors. It kind of makes me think that maybe separating structs and classes was a bad idea. I have yet to ever once say to myself "thank goodness structs and classes are different in D!", though plenty of times I've muttered "dangit why can't I just get a default copy constructor for this class" or "shoot, these static opCalls are annoying" or "dang, I wish I could get a weak reference to a struct", etc. --bb
Aug 15 2007
Russell Lewis Wrote:But the worst of it all, IMHO, is what you (and others) have pointed out: the lack of clarity about whether a declaration is a struct-by-value or class-by-reference. That means that if a container was originally implemented as a struct, you can't ever change it to a class, since that would require rewriting all of the code that uses it. Doesn't that violate one of the basic principles of Object Orientation - the hiding of implementation?I would tend to disagree. Classes are reference types, structs are value types. When I refactor something from a class to a struct, I *expect* the behavior of function arguments to switch from by-refrence to by-value. Furthermore, since classes *cannot* be passed by value, it makes no sense to have additional syntax, which will lead to begginers writing 'Object o;' instead of 'Object* o;' - which will not compile under your suggestion.
Aug 15 2007
Tristam MacDonald wrote:Russell Lewis Wrote:Tristam, your response felt a little "hotter" than I what I prefer. If I sounded a little flamish in my post, I apologize. If I'm misreading you, I also apologize!But the worst of it all, IMHO, is what you (and others) have pointed out: the lack of clarity about whether a declaration is a struct-by-value or class-by-reference. That means that if a container was originally implemented as a struct, you can't ever change it to a class, since that would require rewriting all of the code that uses it. Doesn't that violate one of the basic principles of Object Orientation - the hiding of implementation?I would tend to disagree. Classes are reference types, structs are value types. When I refactor something from a class to a struct, I *expect* the behavior of function arguments to switch from by-refrence to by-value.I think that you made my point for me...if refactoring requires lots of rewrites, then the use-side of the code has too much insight into the implementation. I don't see any fundamental reason why classes need to be reference types, other than history. (Yes, they are currently all on the heap, even scope variables, but even that is a decision that I could change, if there was a reason for it.) That's MHO, anyhow.Furthermore, since classes *cannot* be passed by value, it makes no sense to have additional syntax, which will lead to begginers writing 'Object o;' instead of 'Object* o;' - which will not compile under your suggestion.What's wrong with Object o; ? If I had a magic wand, I would declare that syntax to be equivalent to the current syntax: scope Object o = new Object; or, better, to make it a stack variable. So the current implementation requires "extra syntax" as well (for scope declarations). It's a question of which extra syntax is better. IMHO, making classes consistent with structs is more desirable than eliminating a few stray asteriskes. The beginners who stumble across the "can't pass a class as a by-value function parameter" rule can be easily educated with a well-written error message emitted by the compiler. I don't have a magic wand, and I don't want everybody to have to rewrite their old code. But I continue to hope that this might change in 2.0, or maybe 3.0. :)
Aug 15 2007
Russell Lewis wrote:I don't see any fundamental reason why classes need to be reference types, other than history.What about this situation. //begin C++ class A{ int val; }; class B:public A{ int foo; }; int main(int argc,char**argvs){ A a; B b; a=b;//HERE what happens to b's member foo? } //end C++ it's my impression that D's classes are reference types to avoid that specific problem.
Aug 15 2007
Johan Granberg wrote:Russell Lewis wrote:That's known as the 'slicing' problem. It's pernicious in that it can be extremely hard to expose via testing or code reviews, yet will expose the program to unpredictable behavior. While it is fairly obvious in your example that it is wrong, it can occur in cases that are *impossible* for the compiler to detect: void foo(A* a) { A ax = *a; } void main() { B b; foo(&b); } This kind of error is impossible in D.I don't see any fundamental reason why classes need to be reference types, other than history.What about this situation. //begin C++ class A{ int val; }; class B:public A{ int foo; }; int main(int argc,char**argvs){ A a; B b; a=b;//HERE what happens to b's member foo? } //end C++ it's my impression that D's classes are reference types to avoid that specific problem.
Aug 15 2007
Walter Bright wrote:Johan Granberg wrote:It's trivially detected by various automated tools, which can flag any non-abstract base class. (Such classes almost invariably indicate bad design in any case.) Clearly it would be simple for a compiler to detect when a concrete class was used as a base class. There's no need to remove value semantics in order to solve this problem; it's something of a sledgehammer solution. -- JamesRussell Lewis wrote:That's known as the 'slicing' problem. It's pernicious in that it can be extremely hard to expose via testing or code reviews, yet will expose the program to unpredictable behavior.I don't see any fundamental reason why classes need to be reference types, other than history.What about this situation. //begin C++ class A{ int val; }; class B:public A{ int foo; }; int main(int argc,char**argvs){ A a; B b; a=b;//HERE what happens to b's member foo? } //end C++ it's my impression that D's classes are reference types to avoid that specific problem.
Aug 15 2007
James Dennett Wrote:It's trivially detected by various automated tools, which can flag any non-abstract base class. (Such classes almost invariably indicate bad design in any case.)Now that's just patently untrue. I've never done any serious work in C++ outside of school, so perhaps that's a belief some people hold in the C++ world, but I doubt it's a very common one. I'd say it's about an 80-20 split for code I write and 60-40 in standard libraries. That is, 80% of the time I'm extending something I am indeed extending an abstract base, but 20% of the time I'm extending a concrete class is helpful, and in the case of libraries you can't change, necessary. Here's a real-world example. At work recently (I don't think this violates my NDA...) I was asked to write a fake POP & IMAP server (it gives a certain number of new messages per hour to every account for load testing) in Java. I have a class UserAcct which is used to track persistent accounts so that UIDs remain contiguous. When I added support for IMAP idle, some additional information was needed to handle idling subscriptions/send events/etc. My options were to either create an "imap idle subscription" class to wrap a subscription with a pointer to the UserAcct class (composition) or to extend the UserAcct with a SubscribedUserAcct (only a small fraction of the users would have IMAP idle enabled), or just to add some extra fields to UserAcct. I selected the second option (extension) here. Arguably, composition may have been the better choice from a design standpoint, and I certainly respect this opinion. However: - Composition removes polymorphism: The obvious. If I needed to override or change a function of the base class, this would not be an option with composition. In addition, were I using a wrapper class, I copuld no longer keep a map of "UserAcct" objects, I would need two different data structures, one of the wrapper and one of the accounts. - Composition costs additional memory: the extra 8 bytes (in Java and D, not sure about C++) overhead of the additional object, plus another 4 for the reference (actually, that averages to 6 because the Sun JVM likes to align classes on 8-byte word boundaries). Since I had recently run into some OutOfMemory errors when it was loaded with 50,000+ accounts, I was loath to incur much additional heap usage than I had to. Anyways, this is getting a bit off-topic. However a sweeping statement like "all base classes should be abstract" just seems very wrong to me. I personally think D's behavior is perfect, though I think non-polymorphic struct inheritance (i.e. as syntactic sugar for mixins) and constructors would be nice. The distinction, IMO, shows D to be a more mature language with different use-cases for structs and classes clearly defined. That, and you've mentioned specific idioms in a few of your messages. Idioms are great, but not everybody follows them (especially people new to a language). Making something part of the language standardizes it. That's why having unittests, asserts, preconditions, etc., in D is such a boon: while that could all be done using a library, there'd probably be three or four different libraries out there to do it and 75% of users wouldn't know about them or use those features at all.
Aug 16 2007
Robert Fraser wrote:Making something part of the language standardizes it. That's why having unittests, asserts, preconditions, etc., in D is such a boon: while that could all be done using a library, there'd probably be three or four different libraries out there to do it and 75% of users wouldn't know about them or use those features at all.Right on.
Aug 16 2007
Walter Bright wrote:Robert Fraser wrote:It's true; one problem with C++ is that there is too much diversity in the (huge) set of idioms and libraries, and standardization has many benefits (which is why it's something I work on). Sometimes it *can* even be better to have one preferred approach to something even if it's not technically the greatest, for the other benefits that it can bring. With the complexity of C++, it's a real problem finding developers with enough knowledge to use it safely and efficiently (more so than for C, in my experience, though the best C++ runs rings around C). Simplicity and uniformity has great merit, but does require taking calculated risks and making trade-offs. -- JamesMaking something part of the language standardizes it. That's why having unittests, asserts, preconditions, etc., in D is such a boon: while that could all be done using a library, there'd probably be three or four different libraries out there to do it and 75% of users wouldn't know about them or use those features at all.Right on.
Aug 16 2007
James Dennett wrote:Walter Bright wrote:That's true if you agree with the notion that only abstract classes should be used as base classes, which I do not agree with.That's known as the 'slicing' problem. It's pernicious in that it can be extremely hard to expose via testing or code reviews, yet will expose the program to unpredictable behavior.It's trivially detected by various automated tools, which can flag any non-abstract base class. (Such classes almost invariably indicate bad design in any case.) Clearly it would be simple for a compiler to detect when a concrete class was used as a base class. There's no need to remove value semantics in order to solve this problem; it's something of a sledgehammer solution.
Aug 16 2007
Walter Bright wrote:James Dennett wrote:Then you lose benefits of static type checking for operations such as equality comparisons, unless you have ugly special cases. How does D handle this? Guess I'll go look it up. -- JamesWalter Bright wrote:That's true if you agree with the notion that only abstract classes should be used as base classes, which I do not agree with.That's known as the 'slicing' problem. It's pernicious in that it can be extremely hard to expose via testing or code reviews, yet will expose the program to unpredictable behavior.It's trivially detected by various automated tools, which can flag any non-abstract base class. (Such classes almost invariably indicate bad design in any case.) Clearly it would be simple for a compiler to detect when a concrete class was used as a base class. There's no need to remove value semantics in order to solve this problem; it's something of a sledgehammer solution.
Aug 16 2007
Walter Bright wrote:That's known as the 'slicing' problem. It's pernicious in that it can be extremely hard to expose via testing or code reviews, yet will expose the program to unpredictable behavior.It frustrates me that people keep conflating what are orthogonal questions, such as: - Should we allow copy-assignments of class types? - Should we allow classes on the stack? - Should we use '*' to declare pointers-to-classes? Those are 3 *VERY* different questions. IMHO, we should disallow copy-assignments of class types (compiler detects the problem and spits out a clear error message, thus trivially educating the beginners), but still require the '*'.
Aug 17 2007
Russell Lewis wrote:Walter Bright wrote:And I assume you would still allow classes on the stack in that case. Good point. That way you would retain uniform struct/class syntax, but still avoid the slicing problem. The only down side I can think of is what someone mentioned before. Since you can't use operator overloading on pointers, it would mean either 1) you suffer through lots of dereferencing (*a + *b), or 2) you need to introduce a full-fledged C++-like reference type (a pointer that acts like a value). Or 3) you require a sigil to do all pointer math. More about 3): I was tempted to to say "only for pointer-to-struct/class" but that interferes with the ability for class/struct to mimic built-in syntax. E.g. with this pattern: for (T* p=&TArray[0]; p!=end; p++) {...} you don't want to have to do something different depending upon what T is. With the approach 3) you'd have to make that something like for (T* p=&TArray[0]; p!=end; as_ptr(p)++) {...} intuitive meaning of "treat this as a simple number" instead of something more complex: for (T* p=&TArray[0]; p!=end; #p++) {...} That doesn't seem half bad to me, but it would certainly be annoying for code that does lots of pointer math. --bbThat's known as the 'slicing' problem. It's pernicious in that it can be extremely hard to expose via testing or code reviews, yet will expose the program to unpredictable behavior.It frustrates me that people keep conflating what are orthogonal questions, such as: - Should we allow copy-assignments of class types? - Should we allow classes on the stack? - Should we use '*' to declare pointers-to-classes? Those are 3 *VERY* different questions. IMHO, we should disallow copy-assignments of class types (compiler detects the problem and spits out a clear error message, thus trivially educating the beginners), but still require the '*'.
Aug 17 2007
Johan Granberg wrote:Russell Lewis wrote:// begin non-naive C++ class A { // a base class public: int val; protected: A() {} }; class B : public A { public: int foo; }; int main() { A a; // compilation error here. B b; a = b; // mu } // end C++ It's a slight exaggeration to say that much of D's design exists to compensate for programmers finding it too much trouble/too hard to learn idioms needed to use C++ safely. However, value semantics (such as comparing for equality) don't work well with polymorphism. C++ avoids this problem when used conventionally; I'm not sure if D falls into the same trap as most OO languages by allowing equality comparisons between objects of different classes. -- JamesI don't see any fundamental reason why classes need to be reference types, other than history.What about this situation. //begin C++ class A{ int val; }; class B:public A{ int foo; }; int main(int argc,char**argvs){ A a; B b; a=b;//HERE what happens to b's member foo? } //end C++ it's my impression that D's classes are reference types to avoid that specific problem.
Aug 15 2007
Bill Baxter wrote:I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.Funny. I started out having concerns about the inconsistency here and have decided it's a good thing :-) I think the salient point is that classes in D do not and will never (as far as I know) have a value type--they are always a reference type, even when "scope" is used. However, requiring the user to supply an asterisk for all class variable declarations suggests to me that I can remove the asterisk and get a value type, which I cannot. Back before the addition of "scope" I'd thought the syntax of scoped objects would be more like a value type: MyClass c = MyClass(); in which case I think the presence of an asterisk may have been more justified, though the lack of pass-by-value would likely still have rendered it too confusing to be practical IMO. I think in D we just have to accept that classes are special and deserve special syntax.In large part it all stems from the decision to make classes and structs different. That seems nice in theory, but as time goes on the two are becoming more and more alike.Personally, I think structs and classes are nothing alike in D and I consider this a good thing. One of my pet peeves about C++ is that it has two keywords to represent the same darn thing. The distinction between structs and classes really has more to do with how they are intended to be used than how they look. Structs are simply simple aggregate data types that are allowed to contain functions for convenience. In fact, I think the need for a copy ctor is debatable, given the purpose of structs (even though I've wanted one in the past), as is the need for a dtor. Real ctor support seems entirely reasonable, however, because everything needs to be initialized, and ctors make this both convenient and efficient (the static opCall approach is a confusing hack). Sean
Aug 15 2007
Bill Baxter wrote:I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided. 2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant. 3) Value types just don't work for polymorphic behavior. They must be by reference. There's no way in C++ to ensure that your class instances are used properly by reference only (hence (2)). In fact, in C++, it's *extra work* to use them properly. Value types are fundamentally different from reference types. D gives you the choice.
Aug 15 2007
On Wed, 15 Aug 2007, Walter Bright wrote:Bill Baxter wrote:They're _less_ relevant. There's still the valid usecase of acting like a value to avoid instance sharing where copy construction and assignment's are. This is where it's the author of the class making that decision rather than the user that you talk about in other responses to this thread. That's the distinction between 'by value' vs 'by reference' and 'as a value' vs 'as references'. IE, access vs behavior. I like a strong enforcement and distinction between the access part, but I do believe that it should be possible for a class write to achieve value semantics.I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided. 2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant.3) Value types just don't work for polymorphic behavior. They must be by reference. There's no way in C++ to ensure that your class instances are used properly by reference only (hence (2)). In fact, in C++, it's *extra work* to use them properly. Value types are fundamentally different from reference types. D gives you the choice.
Aug 15 2007
Brad Roberts wrote:On Wed, 15 Aug 2007, Walter Bright wrote:I strongly feel that for that usecase, one should be using a value type, i.e. a struct, not a class. C++ classes are neither one nor the other, thereby doing neither well.Bill Baxter wrote:They're _less_ relevant. There's still the valid usecase of acting like a value to avoid instance sharing where copy construction and assignment's are. This is where it's the author of the class making that decision rather than the user that you talk about in other responses to this thread.I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided. 2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant.That's the distinction between 'by value' vs 'by reference' and 'as a value' vs 'as references'. IE, access vs behavior. I like a strong enforcement and distinction between the access part, but I do believe that it should be possible for a class write to achieve value semantics.I don't agree, I think there is much to be gained by drawing a strong distinction. Value and reference types are *fundamentally* different. Classes are an OOP type, and giving them value semantics introduces all the gotchas C++ has with them (like the slicing problem). However, it is possible to use a struct to wrap a class reference.3) Value types just don't work for polymorphic behavior. They must be by reference. There's no way in C++ to ensure that your class instances are used properly by reference only (hence (2)). In fact, in C++, it's *extra work* to use them properly. Value types are fundamentally different from reference types. D gives you the choice.
Aug 15 2007
Walter Bright wrote:Brad Roberts wrote:The problem I see is that it's really not always clear whether a value or reference type is desired. Currently I'm thinking about container classes. Value semantics are convenient and efficient for small containers. D's built-in containers are basically structs as is often pointed out. If you create lots of little Sets here and there you don't want each Set to require two allocations (one for the object itself, and another for adding some content to it). Only one allocation is really required there. But you might also want your set to implement some interfaces (a la Tango). Then you're forced into a class even though you don't particularly want anyone to subclass your Set. And even though you'd rather use it primarily as a value type (for efficiency, plus since the intent is a 'final' class there will be no derived classes and thus there are no slicing issues). --bbOn Wed, 15 Aug 2007, Walter Bright wrote:I strongly feel that for that usecase, one should be using a value type, i.e. a struct, not a class. C++ classes are neither one nor the other, thereby doing neither well.Bill Baxter wrote:They're _less_ relevant. There's still the valid usecase of acting like a value to avoid instance sharing where copy construction and assignment's are. This is where it's the author of the class making that decision rather than the user that you talk about in other responses to this thread.I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided. 2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant.That's the distinction between 'by value' vs 'by reference' and 'as a value' vs 'as references'. IE, access vs behavior. I like a strong enforcement and distinction between the access part, but I do believe that it should be possible for a class write to achieve value semantics.I don't agree, I think there is much to be gained by drawing a strong distinction. Value and reference types are *fundamentally* different. Classes are an OOP type, and giving them value semantics introduces all the gotchas C++ has with them (like the slicing problem).
Aug 15 2007
Bill Baxter wrote:The problem I see is that it's really not always clear whether a value or reference type is desired. Currently I'm thinking about container classes. Value semantics are convenient and efficient for small containers. D's built-in containers are basically structs as is often pointed out. If you create lots of little Sets here and there you don't want each Set to require two allocations (one for the object itself, and another for adding some content to it). Only one allocation is really required there. But you might also want your set to implement some interfaces (a la Tango). Then you're forced into a class even though you don't particularly want anyone to subclass your Set. And even though you'd rather use it primarily as a value type (for efficiency, plus since the intent is a 'final' class there will be no derived classes and thus there are no slicing issues).If you're using interfaces in a class, it's consuming 8 bytes plus 4 bytes per interface. That means it's cheaper to pass around by reference than by value. I don't think you're going to see a real efficiency gain by adding value semantics for such cases.
Aug 16 2007
Walter Bright wrote:Bill Baxter wrote:Trivially avoided in C++ also.I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided.2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant.Trivial in C++: entity types are declared as "noncopyable", base classes are made abstract, and problems disappear.3) Value types just don't work for polymorphic behavior. They must be by reference. There's no way in C++ to ensure that your class instances are used properly by reference only (hence (2)). In fact, in C++, it's *extra work* to use them properly.Declaring them as noncopyable seems to avoid the problems to which you refer, and should be normal for entity types in C++. (I'm not sure which extra work you're referring to: using some form of GC for memory management?)Value types are fundamentally different from reference types. D gives you the choice.C++ gives you *more* choice by allowing easier migration between the two without imposing performance or syntactic differences. -- James
Aug 15 2007
James Dennett wrote:Walter Bright wrote:Yes, that's pretty much the same conclusion I came to. C++'s way gives you a little more rope. You can use it to hang yourself, or to fashion a nifty lasso if you've learned how to use a lasso. I think Walter's plan for D was always to hit the middle ground between Java ("NO ROPE FOR YOU!") and C++ ("here have some more"), and give you more freedom than Java, but be easier to learn and use than C++. --bbBill Baxter wrote:Trivially avoided in C++ also.I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided.2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant.Trivial in C++: entity types are declared as "noncopyable", base classes are made abstract, and problems disappear.3) Value types just don't work for polymorphic behavior. They must be by reference. There's no way in C++ to ensure that your class instances are used properly by reference only (hence (2)). In fact, in C++, it's *extra work* to use them properly.Declaring them as noncopyable seems to avoid the problems to which you refer, and should be normal for entity types in C++. (I'm not sure which extra work you're referring to: using some form of GC for memory management?)Value types are fundamentally different from reference types. D gives you the choice.C++ gives you *more* choice by allowing easier migration between the two without imposing performance or syntactic differences.
Aug 16 2007
James Dennett wrote:Walter Bright wrote:That isn't my experience. In my work on DMDscript in D, I changed some types between struct and class to try out some variations, and found it to be a lot easier than in C++. For one thing, I didn't have to find and edit all the -> and .'s. But you're right that C++ allows you more choice - you can create a polymorphic type with value semantics. I think D is better off without them, however.Value types are fundamentally different from reference types. D gives you the choice.C++ gives you *more* choice by allowing easier migration between the two without imposing performance or syntactic differences.
Aug 16 2007
Walter Bright wrote:James Dennett wrote:In C++, no, you didn't, as structs and classes are no different in their use of "->" and "." (as you're aware, they're all just classes). Now, if you changed from using objects by value to using them by reference, it would be dangerous to take the D route and use almost the same syntax for both (for example, you inherit the ubiquitous Java bug of writing MyType foo; and finding that foo is null rather than being a usable object). Of course there are two things being conflated here: (1) the difference between structs and classes, and (2) the difference between entity/reference types and value types In D, this "confusion" is not a confusion, as struct/class are there to model the latter distinction. In C++, you can take entity objects and drop them on the stack (in perfectly sane code), and you can take value objects and work with them on the heap (as you can in D, I believe); it makes the two dimensions orthogonal. (This has an analog in access control: in C++ access control and virtual-ness are orthogonal, in Java and D they are not. Orthogonality has advantages, but also has some consequences that are arguably counter-intuitive.)Walter Bright wrote:That isn't my experience. In my work on DMDscript in D, I changed some types between struct and class to try out some variations, and found it to be a lot easier than in C++. For one thing, I didn't have to find and edit all the -> and .'s.Value types are fundamentally different from reference types. D gives you the choice.C++ gives you *more* choice by allowing easier migration between the two without imposing performance or syntactic differences.But you're right that C++ allows you more choice - you can create a polymorphic type with value semantics. I think D is better off without them, however.Your call. I don't agree, but that's fine! -- James
Aug 16 2007
James Dennett wrote:Walter Bright wrote:I meant I switched between value and reference semantics for an object.James Dennett wrote:In C++, no, you didn't, as structs and classes are no different in their use of "->" and "." (as you're aware, they're all just classes).Walter Bright wrote:That isn't my experience. In my work on DMDscript in D, I changed some types between struct and class to try out some variations, and found it to be a lot easier than in C++. For one thing, I didn't have to find and edit all the -> and .'s.Value types are fundamentally different from reference types. D gives you the choice.C++ gives you *more* choice by allowing easier migration between the two without imposing performance or syntactic differences.
Aug 17 2007
Walter Bright wrote:James Dennett wrote:Careful with selective quoting! I recognized that you probably meant this fundamental change of semantics with my very next paragraph, reproduced below:Walter Bright wrote:I meant I switched between value and reference semantics for an object.James Dennett wrote:In C++, no, you didn't, as structs and classes are no different in their use of "->" and "." (as you're aware, they're all just classes).Walter Bright wrote:That isn't my experience. In my work on DMDscript in D, I changed some types between struct and class to try out some variations, and found it to be a lot easier than in C++. For one thing, I didn't have to find and edit all the -> and .'s.Value types are fundamentally different from reference types. D gives you the choice.C++ gives you *more* choice by allowing easier migration between the two without imposing performance or syntactic differences.Similar concept should look similar; similar syntax should have similar semantics. You've chosen to use (almost) the same syntax to mean two very different things, with subtle gotchas such as the simple but pervasive bug noted above. -- JamesNow, if you changed from using objects by value to using them by reference, it would be dangerous to take the D route and use almost the same syntax for both (for example, you inherit the ubiquitous Java bug of writing MyType foo; and finding that foo is null rather than being a usable object).
Aug 18 2007
On Thu, 16 Aug 2007 01:35:17 +0400, Walter Bright <newshound1 digitalmars.com> wrote:Bill Baxter wrote:From my expirience this is a problem of C++ beginners.I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided.2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant. 3) Value types just don't work for polymorphic behavior. They must be by reference. There's no way in C++ to ensure that your class instances are used properly by reference only (hence (2)). In fact, in C++, it's *extra work* to use them properly.But from another side all types in C++ (internal or user defined) is a first class citizens. It is especially important in generic programming, where I can write: template< class T > class ValueHolder { T * m_value public : ValueHolder() : m_value( new T() ) {} ... }; and this code will work as with int, as with std::string, as with ValueHolder<SomeAnotherType>.Value types are fundamentally different from reference types. D gives you the choice.After some languages where there aren't distinction beetwen various kind of types (e.g. C++/Eiffel/Ruby) D looks very strange here. And this is one of the troubles in studying D. -- Regards, Yauheni Akhotnikau
Aug 16 2007
eao197 wrote:On Thu, 16 Aug 2007 01:35:17 +0400, Walter Bright <newshound1 digitalmars.com> wrote:I hear you. Fortunately it's pretty trivial to throw some static-if-is-zzle at the problem in D. May not be so pretty, but it's straightforward at least. --bb3) Value types just don't work for polymorphic behavior. They must be by reference. There's no way in C++ to ensure that your class instances are used properly by reference only (hence (2)). In fact, in C++, it's *extra work* to use them properly.But from another side all types in C++ (internal or user defined) is a first class citizens. It is especially important in generic programming, where I can write: template< class T > class ValueHolder { T * m_value public : ValueHolder() : m_value( new T() ) {} ... }; and this code will work as with int, as with std::string, as with ValueHolder<SomeAnotherType>.
Aug 16 2007
On Thu, 16 Aug 2007 12:28:07 +0400, Bill Baxter <dnewsgroup billbaxter.com> wrote:I suppose that the problem becomes more complex in the case when template class has some methods which receive or return objects by reference or by value. In C++ it is straightforward, but in D it is required some static-if-metaprogramming. -- Regards, Yauheni AkhotnikauI hear you. Fortunately it's pretty trivial to throw some static-if-is-zzle at the problem in D. May not be so pretty, but it's straightforward at least.3) Value types just don't work for polymorphic behavior. They must be by reference. There's no way in C++ to ensure that your class instances are used properly by reference only (hence (2)). In fact, in C++, it's *extra work* to use them properly.But from another side all types in C++ (internal or user defined) is a first class citizens. It is especially important in generic programming, where I can write: template< class T > class ValueHolder { T * m_value public : ValueHolder() : m_value( new T() ) {} ... }; and this code will work as with int, as with std::string, as with ValueHolder<SomeAnotherType>.
Aug 16 2007
Bill Baxter Wrote:eao197 wrote:There's an example going to the reverse direction: I recently worked on a GMP port in D. For this case, one _must_ avoid typing * to ensure that x = b + c; works. (Nobody wants to type *x = *b + *c, I guess) With C++, there would be a temporary for (b+c) which is assigned to x later - to avoid this copying, the GMP library authors used heavy template metaprogramming (which is ugly to read, although straightforward). In D I'm happy that I just can write a GMP class, overload the operators and use it as x = b + c; - There is temporary construction, but the assignment of (b+c) to a is just copying the pointer and thus no performance penalty. What about letting "new int" do nothing and new int (2) be a simple "2"? Then, T foo = new T(); and T foo = new T(2); would work, too. best regards MatthiasOn Thu, 16 Aug 2007 01:35:17 +0400, Walter Bright <newshound1 digitalmars.com> wrote:I hear you. Fortunately it's pretty trivial to throw some static-if-is-zzle at the problem in D. May not be so pretty, but it's straightforward at least. --bb3) Value types just don't work for polymorphic behavior. They must be by reference. There's no way in C++ to ensure that your class instances are used properly by reference only (hence (2)). In fact, in C++, it's *extra work* to use them properly.But from another side all types in C++ (internal or user defined) is a first class citizens. It is especially important in generic programming, where I can write: template< class T > class ValueHolder { T * m_value public : ValueHolder() : m_value( new T() ) {} ... }; and this code will work as with int, as with std::string, as with ValueHolder<SomeAnotherType>.
Aug 16 2007
eao197 wrote:From my expirience this is a problem of C++ beginners.I was talking to a person who is a C++ developer for a major game company just today. He told me that it is very difficult to find experienced C++ developers, hire them, or even recognize them in a job interview. When you are able to hire them, they cost plenty. It's true that every problem with C++ can be solved by getting more advanced C++ programmers. The problem is getting those C++ programmers. And even the best ones have bad days and make mistakes <g>. Defining a problem out of existence is preferable, cheaper, and more reliable than depending on convention or more training. If I was paying top dollar for a programming expert, I'd rather he focused his energies on something more productive than dodging C++'s potholes. If I was in charge of writing software that absolutely, positively must work correctly, I'll prefer a language guarantee over reliance that my programmers, no matter how good they are, didn't overlook something.
Aug 16 2007
On Thu, 16 Aug 2007 13:20:28 +0400, Walter Bright <newshound1 digitalmars.com> wrote:I know. But I know that getting experienced programmer is very hard for _any_ language. And they cost plenty.From my expirience this is a problem of C++ beginners.I was talking to a person who is a C++ developer for a major game company just today. He told me that it is very difficult to find experienced C++ developers, hire them, or even recognize them in a job interview. When you are able to hire them, they cost plenty. It's true that every problem with C++ can be solved by getting more advanced C++ programmers. The problem is getting those C++ programmers. And even the best ones have bad days and make mistakes <g>.Defining a problem out of existence is preferable, cheaper, and more reliable than depending on convention or more training. If I was paying top dollar for a programming expert, I'd rather he focused his energies on something more productive than dodging C++'s potholes. If I was in charge of writing software that absolutely, positively must work correctly, I'll prefer a language guarantee over reliance that my programmers, no matter how good they are, didn't overlook something.I'm totaly agree with you. This is why I'm interested in D, not in C++. But in the case with value type/reference type separation there are some benefits and drawbacks in the current D version. So I think that 'slicing' is not an important argument in defense of such separation. Because value/reference separation it is a fundamental feature of D so it is better to concentrate to minimization of value/reference/pointer conceptions. For example, what's about removing 'reference' at all? Lets D has only values and pointers: int i; // value. int * pi; // pointer to value. struct S { ... } S s; // value. S * ps; // pointer to value. class C { ... } C c; // Error! Value type cannot be used as value. C * c; // OK. Pointer to reference type. void f( int i, S s, S * s, C * c ) { ... } -- Regards, Yauheni Akhotnikau
Aug 16 2007
On Thu, 16 Aug 2007 21:04:23 +0400, eao197 <eao197 intervale.ru> wrote:class C { ... } C c; // Error! Value type cannot be used as value. C * c; // OK. Pointer to reference type.Oops! Typo :(( Should be: C c; // Error! Reference type cannot be used as value. -- Regards, Yauheni Akhotnikau
Aug 16 2007
eao197 wrote:I'm totaly agree with you. This is why I'm interested in D, not in C++. But in the case with value type/reference type separation there are some benefits and drawbacks in the current D version. So I think that 'slicing' is not an important argument in defense of such separation. Because value/reference separation it is a fundamental feature of D so it is better to concentrate to minimization of value/reference/pointer conceptions. For example, what's about removing 'reference' at all? Lets D has only values and pointers: int i; // value. int * pi; // pointer to value. struct S { ... } S s; // value. S * ps; // pointer to value. class C { ... } C c; // Error! Value type cannot be used as value. C * c; // OK. Pointer to reference type. void f( int i, S s, S * s, C * c ) { ... }But I think if one pulls on that string, one eventually winds up with how C++ does it, with all of the problems.
Aug 16 2007
On Thu, 16 Aug 2007 22:36:53 +0400, Walter Bright = <newshound1 digitalmars.com> wrote:eao197 wrote:+.I'm totaly agree with you. This is why I'm interested in D, not in C+=But in the case with value type/reference type separation there are ==some benefits and drawbacks in the current D version. So I think that='slicing' is not an important argument in defense of such separation.=so =Because value/reference separation it is a fundamental feature of D =r =it is better to concentrate to minimization of value/reference/pointe=conceptions. For example, what's about removing 'reference' at all? ==Lets D has only values and pointers: int i; // value. int * pi; // pointer to value. struct S { ... } S s; // value. S * ps; // pointer to value. class C { ... } C c; // Error! Value type cannot be used as value. C * c; // OK. Pointer to reference type. void f( int i, S s, S * s, C * c ) { ... }But I think if one pulls on that string, one eventually winds up with =how C++ does it, with all of the problems.(excuse me, but my poor English didn't allow me fully understand that. I= = could understand that you fear that such approach is too C++ish and it = would lead to repeating typical C++ errors in D code) I think such fear is a little strange for the language that already has = = pointer arithmetics and facilities like malloc/free/delete (and targetin= g = to the same niches as C++). But let to return to your goal: "Defining a problem out of existence is = = preferable, cheaper, and more reliable than depending on convention or = more training." How about eluminating such problem as null pointers (I = think it is much more common and dangerous problem than object slicing).= targeted this problem by 'non-null' references (pointers). For example, = in = Nice there are two kinds of reference declarations: class C {} C non_null =3D ...; // Can't be null. C? nullable =3D ...; // Can be null. void f( C non_null_arg ) { ... } // non_null_arg can't be null. non_null =3D nullable; // Error! Checked by compiler. f( nullable ); // Error! Checked by compiler. nullable =3D non_null; // Ok. f( non_null ); // Ok. if( null !=3D nullable ) f( nullable ); // Ok. In this branch nullable is not null. I think if there was only one reference/pointer type in D than it would = be = easier to add such non-null references into the language. For example, i= t = is possible to use another symbol for pointers: class C {}; C * nullable =3D ...; // Can be null. non_null =3D nullable; // Error! Checked by compiler. f( nullable ); // Error! Checked by compiler. nullable =3D non_null; // Ok. f( non_null ); // Ok. if( nullable !is null ) f( nullable ); // Ok. In this branch nullable is not null. It would be much cleaner (imho) than something like: C nullable_ref =3D ...; // Reference, can be null. C * nullable_ptr =3D ...; // Pointer, can be null. [1] http://nice.sf.net [2] http://se.ethz.ch/~meyer/publications/lncs/attached.pdf [3] http://research.microsoft.com/specsharp/papers/krml136.pdf -- = Regards, Yauheni Akhotnikau
Aug 16 2007
"eao197" <eao197 intervale.ru> wrote in message news:op.tw6uouycsdcfd2 eao197nb2.intervale.ru... On Thu, 16 Aug 2007 22:36:53 +0400, Walter Bright <newshound1 digitalmars.com> wrote:But let to return to your goal: "Defining a problem out of existence is preferable, cheaper, and more reliable than depending on convention or more training." How about eluminating such problem as null pointers (I think it is much more common and dangerous problem than object slicing).Because null pointers are easy to debug. And it's a lot less dangerous because you get an exception generated when you try to use a null pointer. Where as with the slicing problem anything can happen and it's incredibly hard to debug.
Aug 17 2007
On Fri, 17 Aug 2007 18:58:19 +0400, Jb <jb nowhere.com> wrote:"eao197" <eao197 intervale.ru> wrote in message news:op.tw6uouycsdcfd2 eao197nb2.intervale.ru... On Thu, 16 Aug 2007 22:36:53 +0400, Walter Bright <newshound1 digitalmars.com> wrote:It is, probably, a good consolidation when null-pointer error happens in production code.But let to return to your goal: "Defining a problem out of existence is preferable, cheaper, and more reliable than depending on convention or more training." How about eluminating such problem as null pointers (I think it is much more common and dangerous problem than object slicing).Because null pointers are easy to debug. And it's a lot less dangerous because you get an exception generated when you try to use a null pointer.Where as with the slicing problem anything can happen and it's incredibly hard to debug.Any examples? What's bad and dangerous happens in the case of slicing? -- Regards, Yauheni Akhotnikau
Aug 17 2007
"eao197" <eao197 intervale.ru> wrote in message news:op.tw7obrzwsdcfd2 eao197nb2.intervale.ru...On Fri, 17 Aug 2007 18:58:19 +0400, Jb <jb nowhere.com> wrote:Never mind, i had misunderstood what the slicing problem was. Sorry. jbWhere as with the slicing problem anything can happen and it's incredibly hard to debug.Any examples? What's bad and dangerous happens in the case of slicing?
Aug 17 2007
On Fri, 17 Aug 2007 21:46:16 +0400, Jb <jb nowhere.com> wrote:"eao197" <eao197 intervale.ru> wrote in message news:op.tw7obrzwsdcfd2 eao197nb2.intervale.ru...No problem. It is good that D has less problems in language than C++. D hasn't slicing problem -- it is good. But having values, pointers and references (which can be used only with classes) seems too overcomplicated for me. And if we would have only values and pointes it could make simple solutiton for null-pointers problem (may be not simple, because Nice has some bugs, but more simple that in case of references, I think). -- Regards, Yauheni AkhotnikauOn Fri, 17 Aug 2007 18:58:19 +0400, Jb <jb nowhere.com> wrote:Never mind, i had misunderstood what the slicing problem was. Sorry.Where as with the slicing problem anything can happen and it's incredibly hard to debug.Any examples? What's bad and dangerous happens in the case of slicing?
Aug 17 2007
I'm sorry for my agressive behaviour, I was wrong. On Fri, 17 Aug 2007 09:25:34 +0400, eao197 <eao197 intervale.ru> wrote:I think if there was only one reference/pointer type in D than it woul=d =be easier to add such non-null references into the language. For =example, it is possible to use another symbol for pointers: class C {}; C * nullable =3D ...; // Can be null. non_null =3D nullable; // Error! Checked by compiler. f( nullable ); // Error! Checked by compiler. nullable =3D non_null; // Ok. f( non_null ); // Ok. if( nullable !is null ) f( nullable ); // Ok. In this branch nullable is not null.I totally forgot about operator overloading. So it is obviously necessar= y = to have not only values and pointers, but also references. I think that if introduce special syntax for references than problem for= = non-null references could be solved. Moreover with explicitely defined = references it is possible to remove 'in'/'out' argument modifiers. reference: int i; // Value. int i; // Nullable reference to int. int * p; // Nullable pointer to int. struct S; S s; // Value. S s; S * s; class C; C c; // Error! C is a reference type. C c; C * c; // Various kinds of arguments. void f( // Pass by value. S a1, // Pass by reference, like 'out' argument. S a2, // Pass by reference, like 'in' argument. const(S) a3, // Same as two previous, but require non-null references. // Pass by pointer, like 'out' argument. S * a6, // Pass by pointer, like 'in' argument. const(S) * a7 ); I don't think that non-null pointer has much sence. And if we try to = introduce non-null pointers we can end up with things like non-null = pointer to nullable pointer to non-null pointer to something. Unfortunately, syntax 'C c' is ugly in comparision with current 'C c'.= = But I hope it make type system more consistent -- it is easy to = distinguish values form references in code. -- = Regards, Yauheni Akhotnikau
Aug 17 2007
Walter Bright wrote:I was talking to a person who is a C++ developer for a major game company just today. He told me that it is very difficult to find experienced C++ developers, hire them, or even recognize them in a job interview. When you are able to hire them, they cost plenty.I can second this. I'm a game developer, and I also happen to do most of the interviewing and hiring for my group. C++ and entry-level (i.e. fresh out of college) is almost non-existent. There's quite a bit of "java syndrome" going on out there. Experienced C++ game devs are indeed expensive. And also, *'s suck. Please don't infect D with this. --Steve
Aug 16 2007
Stephen Waits wrote:Walter Bright wrote:Right. One of the goals of D is to enable regular programmers to get better results. Programming ordinary apps shouldn't require the top tier programmers. One reason Java, Ruby and VB are so popular is because this is true for them.I was talking to a person who is a C++ developer for a major game company just today. He told me that it is very difficult to find experienced C++ developers, hire them, or even recognize them in a job interview. When you are able to hire them, they cost plenty.I can second this. I'm a game developer, and I also happen to do most of the interviewing and hiring for my group. C++ and entry-level (i.e. fresh out of college) is almost non-existent. There's quite a bit of "java syndrome" going on out there. Experienced C++ game devs are indeed expensive.And also, *'s suck. Please don't infect D with this.I'm happy to debate this as I feel it is important to explain why this design is the way it is, but don't worry, I feel very strongly that D's behavior here is the right thing.
Aug 16 2007
Walter Bright wrote:Stephen Waits wrote:amenWalter Bright wrote:Right. One of the goals of D is to enable regular programmers to get better results. Programming ordinary apps shouldn't require the top tier programmers. One reason Java, Ruby and VB are so popular is because this is true for them.I was talking to a person who is a C++ developer for a major game company just today. He told me that it is very difficult to find experienced C++ developers, hire them, or even recognize them in a job interview. When you are able to hire them, they cost plenty.I can second this. I'm a game developer, and I also happen to do most of the interviewing and hiring for my group. C++ and entry-level (i.e. fresh out of college) is almost non-existent. There's quite a bit of "java syndrome" going on out there. Experienced C++ game devs are indeed expensive.
Aug 16 2007
Walter Bright wrote:eao197 wrote:An important observation. And given that programmers starting out in their careers are often looking at more "modern" languages, the pool of competent C++ developers isn't growing fast, while demand is still pretty strong.From my expirience this is a problem of C++ beginners.I was talking to a person who is a C++ developer for a major game company just today. He told me that it is very difficult to find experienced C++ developers, hire them, or even recognize them in a job interview. When you are able to hire them, they cost plenty.It's true that every problem with C++ can be solved by getting more advanced C++ programmers. The problem is getting those C++ programmers. And even the best ones have bad days and make mistakes <g>.I assure you, on bad days I have made mistakes in many, many languages ;)Defining a problem out of existence is preferable, cheaper, and more reliable than depending on convention or more training. If I was paying top dollar for a programming expert, I'd rather he focused his energies on something more productive than dodging C++'s potholes.And if you get an expert who knows C++, she won't spend much time avoiding its potholes.If I was in charge of writing software that absolutely, positively must work correctly, I'll prefer a language guarantee over reliance that my programmers, no matter how good they are, didn't overlook something.Yup; I look forward to a future in which correctness proofs play a larger role in practical situations. -- James
Aug 16 2007
Bill Baxter wrote:I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.The simple distinction between reference types and value types has irked me from the very beginning. As a programmer using something that somebody else wrote, I shouldn't have to know its storage type. Distinctions between the two pop up here and there throughout D code. For example, value_type[] and ref_type[] have completely different copy behaviors.Bad points: - Harder to tell whether you're dealing with a pointer or not (c.f. the common uninitialized 'MyObject obj;' bug) - To build on the stack, have to use 'scope' Good points: + No need to type '*' everywhere when you use class objects + ??? anything else ???I was thinking recently of an interesting syntax twist... Always require & when trying to get to an address, and use the raw variable name and "." to refer to whatever is pointed to. Obtaining an address would require &. It's an interesting change, but would likely be too confusing to switch over. Somehow I bet even suggesting the idea will mark me as a crack pot :)
Aug 15 2007
Jason House wrote:Bill Baxter wrote:Well realistically none of this is likely to change anyway, so you're free to suggest anything you want. :-) I was thinking about something like that as well, though. But couldn't really think how to make it useful. My thinking was that in D we have classes sort of "shifted" by one from structs pointer-to-pointer pointer value struct &(&p) &p p struct* &p p *p class &p p N/A So instead of that make structs default to pointers too, to shift everything back to be the same: pointer-to-pointer pointer value struct &p p *p class &p p N/A indicator. So to do a struct-by-value you'd declare: while MyStruct Bar; would be a pointer/reference just like MyClass is. Some problems are: - what do you do about built-in value types like 'int'? Doesn't make sense to make 'int x' be a pointer type, IMHO. And if the built-in types behave differently from structs have you gained much? - for structs, as-value should be the common case, so you'll have to use - it still won't make the struct -> class transition all that much easier unless classes gain the ability to be passed around by value. --bbI'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.The simple distinction between reference types and value types has irked me from the very beginning. As a programmer using something that somebody else wrote, I shouldn't have to know its storage type. Distinctions between the two pop up here and there throughout D code. For example, value_type[] and ref_type[] have completely different copy behaviors.Bad points: - Harder to tell whether you're dealing with a pointer or not (c.f. the common uninitialized 'MyObject obj;' bug) - To build on the stack, have to use 'scope' Good points: + No need to type '*' everywhere when you use class objects + ??? anything else ???I was thinking recently of an interesting syntax twist... Always require & when trying to get to an address, and use the raw variable name and "." to refer to whatever is pointed to. Obtaining an address would require &. It's an interesting change, but would likely be too confusing to switch over. Somehow I bet even suggesting the idea will mark me as a crack pot :)
Aug 15 2007
Bill Baxter wrote:Jason House wrote:I was thinking of something more consistent. pointer-to-pointer pointer value struct N/A &p p struct* &&p &p p class &&p &p p T ??? &p p simply doing p will always give the value and &p will always give a pointer. How many &'s allowed would be specific to the circumstance/implementation. In my naive view, the last valid pointer (&p for struct, &&p for struct*, etc...) would have to be const. T above is supposed to be some kind of templated typeI was thinking recently of an interesting syntax twist... Always require & when trying to get to an address, and use the raw variable name and "." to refer to whatever is pointed to. Obtaining an address would require &. It's an interesting change, but would likely be too confusing to switch over. Somehow I bet even suggesting the idea will mark me as a crack pot :)Well realistically none of this is likely to change anyway, so you're free to suggest anything you want. :-) I was thinking about something like that as well, though. But couldn't really think how to make it useful. My thinking was that in D we have classes sort of "shifted" by one from structs pointer-to-pointer pointer value struct &(&p) &p p struct* &p p *p class &p p N/A So instead of that make structs default to pointers too, to shift everything back to be the same: pointer-to-pointer pointer value struct &p p *p class &p p N/A
Aug 16 2007
Jason House wrote:Bill Baxter wrote:Oh, I see. So you mean that no matter what, the compiler would figure out what dereferencing was necessary, and you always have to explicitly say whether you want a pointer or not. Interesting. So pointer math becomes pretty tedious for one. Maybe that's ok. And then what happens when you're given some T (which could be pointer to pointer to int, say) and you want dereference it one level further? And what about aliases? If I have alias int* intp; alias int** intpp; intp x; Then x can simultaneously be described as int**, intp*, or intpp. But why is struct pointer-to-pointer N/A. Shouldn't the N/A be under 'value' for class? --bbJason House wrote:I was thinking of something more consistent. pointer-to-pointer pointer value struct N/A &p p struct* &&p &p p class &&p &p p T ??? &p pI was thinking recently of an interesting syntax twist... Always require & when trying to get to an address, and use the raw variable name and "." to refer to whatever is pointed to. Obtaining an address would require &. It's an interesting change, but would likely be too confusing to switch over. Somehow I bet even suggesting the idea will mark me as a crack pot :)Well realistically none of this is likely to change anyway, so you're free to suggest anything you want. :-) I was thinking about something like that as well, though. But couldn't really think how to make it useful. My thinking was that in D we have classes sort of "shifted" by one from structs pointer-to-pointer pointer value struct &(&p) &p p struct* &p p *p class &p p N/A So instead of that make structs default to pointers too, to shift everything back to be the same: pointer-to-pointer pointer value struct &p p *p class &p p N/A
Aug 16 2007
Bill Baxter wrote:Jason House wrote:It also doesn't help clarity (completely) because you can still have typedefs and other user-defined types that would have value semantics.Bill Baxter wrote:Well realistically none of this is likely to change anyway, so you're free to suggest anything you want. :-) I was thinking about something like that as well, though. But couldn't really think how to make it useful. My thinking was that in D we have classes sort of "shifted" by one from structs pointer-to-pointer pointer value struct &(&p) &p p struct* &p p *p class &p p N/A So instead of that make structs default to pointers too, to shift everything back to be the same: pointer-to-pointer pointer value struct &p p *p class &p p N/A indicator. So to do a struct-by-value you'd declare: while MyStruct Bar; would be a pointer/reference just like MyClass is. Some problems are: - what do you do about built-in value types like 'int'? Doesn't make sense to make 'int x' be a pointer type, IMHO. And if the built-in types behave differently from structs have you gained much? - for structs, as-value should be the common case, so you'll have to use - it still won't make the struct -> class transition all that much easier unless classes gain the ability to be passed around by value.I'm starting to seriously wonder if it was a good idea to hide the pointers to classes. It seemed kinda neat when I was first using D that I could avoid typing *s all the time. But as time wears on it's starting to seem more like a liability.The simple distinction between reference types and value types has irked me from the very beginning. As a programmer using something that somebody else wrote, I shouldn't have to know its storage type. Distinctions between the two pop up here and there throughout D code. For example, value_type[] and ref_type[] have completely different copy behaviors.Bad points: - Harder to tell whether you're dealing with a pointer or not (c.f. the common uninitialized 'MyObject obj;' bug) - To build on the stack, have to use 'scope' Good points: + No need to type '*' everywhere when you use class objects + ??? anything else ???I was thinking recently of an interesting syntax twist... Always require & when trying to get to an address, and use the raw variable name and "." to refer to whatever is pointed to. Obtaining an address would require &. It's an interesting change, but would likely be too confusing to switch over. Somehow I bet even suggesting the idea will mark me as a crack pot :)
Aug 17 2007