www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Notes on Defective C++

reply bearophile <bearophileHUGS lycos.com> writes:
The following is a list of some of the main defects of C++, as seen by Yossi
Kreinin:
http://yosefk.com/c++fqa/defective.html

So I have tried to see how D1/D2 score in such regard. In the following list *
denotes a point that (I think) D doesn't address enough.
Note that I'm ignorant of C++ and my experience of D has so holes still, so
it's probable for me to have done several mistakes. (If you are kind you may
help fix my mistakes).

1) No compile time encapsulation: "In naturally written C++ code, changing the
private members of a class requires recompilation of the code using the class."
I think D solves this problem, you only have to compile the module the contains
the class (generally a module contains related classes/functions).
2) Outstandingly complicated grammar: D isn't a simple language, but I think
more or less solves this problem (it was one of the points of creating D in the
first place).
3) No way to locate definitions: I think this is solved.
4) No run time encapsulation: this is fixed regarding array bounds (but not
regarding other things). You can even compile some modules with "-release" and
others without.
5) No binary implementation rules: fixed, more or less.
*6) No reflection: I think D already keeps at runtime most of the relevant
data, but the current ways and syntax to use it aren't enough.
*7) Very complicated type system: this is quite difficult to fix, maybe it
can't be fixed.
8) Very complicated type-based binding rules: I think this is more or less
fixed.
9) Defective operator overloading: I think this is fixed.
10) Defective exceptions: I think this is fixed.
*11) Duplicate facilities: this isn't fixed. The author says the following, but
things aren't so easy: "there's absolutely NO technical reason to parse C-like
syntax in order to work with existing C code since that code can be compiled
separately."  In D several things can be done in the D way or C way. This is
good because it allows you to convert C code to D quickly, but increases the
language complexity (see static ways to define a struct, plus the way
introduced by D), makes some C syntax not available for something better in D,
and probably confuses D newbies too (or generally people that will try to learn
D with little/no prior knowledge of C). So I think removing some C ways,
expecially the most redundant or error-prone may be good.
12) No high-level built-in types: D has some very useful built-in data
structures, but they lack several useful methods (lazy views, dict cleaning,
dict equality, etc etc) and one or two more can be added (a set? Multiprecision
integer? A duck type?).
13) Manual memory management: fixed.
14) Defective metaprogramming facilities: there's always space for improvements.
15) Unhelpful standard library: Tango, etc, I think this is fixed enough.
16) Defective inlining: I think this is fixed.
17) Implicitly called & generated functions: I think this is mostly fixed.

So I think D comes out well, the main points missing still are a better
reflection, better builtins, and some more C redundancy removed.

Bye,
bearophile
Dec 07 2008
next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
bearophile wrote:
 1) No compile time encapsulation: "In naturally written C++ code,
 changing the private members of a class requires recompilation of the
 code using the class." I think D solves this problem, you only have
 to compile the module the contains the class (generally a module
 contains related classes/functions).

D doesn't solve this problem. Changing the private members changes offsets of other members and derived classes, so they must all be recompiled. Inline functions are also, of course, affected. The way to avoid this is to use interfaces.
Dec 07 2008
parent reply "Nick Sabalausky" <a a.a> writes:
"Walter Bright" <newshound1 digitalmars.com> wrote in message 
news:ghh7r5$436$1 digitalmars.com...
 bearophile wrote:
 1) No compile time encapsulation: "In naturally written C++ code,
 changing the private members of a class requires recompilation of the
 code using the class." I think D solves this problem, you only have
 to compile the module the contains the class (generally a module
 contains related classes/functions).

D doesn't solve this problem. Changing the private members changes offsets of other members and derived classes, so they must all be recompiled. Inline functions are also, of course, affected. The way to avoid this is to use interfaces.

Would there be any problem with making all the public members come first, then the protected, then the private? That way changing private members wouldn't change the offsets of public and protected members. (Not that I consider this a major issue.)
Dec 07 2008
next sibling parent Sean Kelly <sean invisibleduck.org> writes:
Nick Sabalausky wrote:
 "Walter Bright" <newshound1 digitalmars.com> wrote in message 
 news:ghh7r5$436$1 digitalmars.com...
 bearophile wrote:
 1) No compile time encapsulation: "In naturally written C++ code,
 changing the private members of a class requires recompilation of the
 code using the class." I think D solves this problem, you only have
 to compile the module the contains the class (generally a module
 contains related classes/functions).

of other members and derived classes, so they must all be recompiled. Inline functions are also, of course, affected. The way to avoid this is to use interfaces.

Would there be any problem with making all the public members come first, then the protected, then the private? That way changing private members wouldn't change the offsets of public and protected members. (Not that I consider this a major issue.)

With inheritance, I'm not sure this is possible. class A { public int a; private int b; } class B : A { public int c; private int d; } Sean
Dec 07 2008
prev sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Nick Sabalausky wrote:
 "Walter Bright" <newshound1 digitalmars.com> wrote in message 
 Changing the private members changes offsets
 of other members and derived classes, so they must all be recompiled. 
 Inline functions are also, of course, affected.

 The way to avoid this is to use interfaces.

Would there be any problem with making all the public members come first, then the protected, then the private? That way changing private members wouldn't change the offsets of public and protected members. (Not that I consider this a major issue.)

It still would affect any derived classes.
Dec 07 2008
prev sibling next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"bearophile" <bearophileHUGS lycos.com> wrote in message 
news:ghh4ps$139f$1 digitalmars.com...
 12) No high-level built-in types: D has some very useful built-in data 
 structures, but they lack several useful methods (lazy views, dict 
 cleaning, dict equality, etc etc) and one or two more can be added (a set? 
 Multiprecision integer? A duck type?).

I think you might be confusing "duck" with "dynamic/variant". Duck typing doesn't refer to any particular data type, unlike dynamic typing which implies a "variant" data type.
Dec 07 2008
parent bearophile <bearophileHUGS lycos.com> writes:
Nick Sabalausky:
 I think you might be confusing "duck" with "dynamic/variant". Duck typing 
 doesn't refer to any particular data type, unlike dynamic typing which 
 implies a "variant" data type.

I was referring to the built-in duck "type" of the Boo language... it's a kind of variant, with few more bells. Bye, bearophile
Dec 08 2008
prev sibling next sibling parent Leandro Lucarella <llucax gmail.com> writes:
bearophile, el  7 de diciembre a las 13:31 me escribiste:
 15) Unhelpful standard library: Tango, etc, I think this is fixed enough.

I don't think this is fair. Unfortunately Tango is *not* the standard library. If you say this is fixed because of Tango, one could say that it's fixed in C++ too because of boost. -- Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/ ---------------------------------------------------------------------------- GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05) ---------------------------------------------------------------------------- No tengo alas, como un planeador. No tengo luces, como un plato volador. Perdi mi rumbo soy un auto chocador.
Dec 08 2008
prev sibling parent reply "Jim Hewes" <jimhewes gmail.com> writes:
On that web page he says that "the lack of garbage collection makes C++ 
exceptions ... inherently defective." I'm not sure I agree with that. I 
think the opposite is more true. When you're using garbage collection, you 
can't rely on destructors to release resources (notably non-memory 
resources) when exceptions occur. In C# for example, the solution is 
supposed to be the 'using' statment, but that's only useful in the context 
of a function. It doesn't help you when you want class A to be a member of 
another class B and it's lifetime to be governed by the lifetime of class B.

The argument on that web page is that exceptions are defective because 
writing exception-safe code is hard. Well, to me, writing code that uses 
return value error codes and diligently handles all of them is just as hard 
or harder. Especially when you have many layers. And it's more likely that 
you can miss one and your program will blindly continue to execute as if 
everything's OK.

Jim
Dec 08 2008
next sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Jim Hewes wrote:
 On that web page he says that "the lack of garbage collection makes C++ 
 exceptions ... inherently defective." I'm not sure I agree with that. I 
 think the opposite is more true. When you're using garbage collection, 
 you can't rely on destructors to release resources (notably non-memory 
 resources) when exceptions occur. In C# for example, the solution is 
 supposed to be the 'using' statment, but that's only useful in the 
 context of a function. 

In D, you have scope guards, which accomplish the same thing as using in C#. Except with more granularity. And what are you going to throw an exception from, besides a function? I think you are talking about situations like this: class A { private File file; this () { file = new File ("somePath"); } // some operations with side effects that maybe close the file } void foo () { auto a = new A; // I want to make sure A's file is cleaned up...how? }
Dec 08 2008
parent reply "Jim Hewes" <jimhewes gmail.com> writes:
"Christopher Wright" <dhasenan gmail.com> wrote in message 
news:ghkdnd$e95$1 digitalmars.com...
 And what are you going to throw an exception from, besides a function? I 
 think you are talking about situations like this:

 class A
 {
    private File file;
    this () { file = new File ("somePath"); }
    // some operations with side effects that maybe close the file
 }

 void foo ()
 {
    auto a = new A;
    // I want to make sure A's file is cleaned up...how?
 }

Yes. Thanks for the example. I do that sort of thing a lot, and it applies to anything with a handle such as mutexes, files, etc. In garbage-collected languages, what am I supposed to do there? It would seem that garbage collection and exceptions don't play nice together. Or am I missing something simple? Could a garbage-collected language ever figure out how to handle this? When class A goes out of scope, how would it know that the file object is not being referenced elsewhere? I wonder if there's a way that reference counting could be used in these cases. Jim
Dec 08 2008
parent reply Christopher Wright <dhasenan gmail.com> writes:
Jim Hewes wrote:
 Yes. Thanks for the example. I do that sort of thing a lot, and it 
 applies to anything with a handle such as mutexes, files, etc.  In 
 garbage-collected languages, what am I supposed to do there? It would 
 seem that garbage collection and exceptions don't play nice together. Or 
 am I missing something simple?

In this case, File should close itself in its destructor. Class A can't close its file in its destructor because the file might have already been collected and finalized. And you can't use a scope guard in this case because A doesn't let you just close the file. I'd say that, if File doesn't close itself in its destructor, that's an error. If File has this error, A should offer a way to close the file without side effects.
Dec 09 2008
parent reply "Jim Hewes" <jimhewes gmail.com> writes:
"Christopher Wright" <dhasenan gmail.com> wrote in message 
news:ghlntc$ku3$1 digitalmars.com...
 In this case, File should close itself in its destructor. Class A can't 
 close its file in its destructor because the file might have already been 
 collected and finalized. And you can't use a scope guard in this case 
 because A doesn't let you just close the file.

 I'd say that, if File doesn't close itself in its destructor, that's an 
 error. If File has this error, A should offer a way to close the file 
 without side effects.

Well, File can clean up after itself in its own destructor, but the problem is you don't know when that will happen. This might be less of a problem with a file if you're not going to use that file again. But it might be more of a problem for things like mutexes that you want to be released right away. The solution in C# is that the class must provide a Dispose function. But then any owning class must remember to also provide a Dispose function and dispose of the disposable objects it owns. If you need to remember this, it becomes a source of possible error. It you've looked at the code (http://msdn.microsoft.com/en-us/library/system.idisposable.dispose.aspx) it's a bunch of extra stuff you need to do. It's kind of like going back to the C++ way of using new/delete for memory, having to remember to call delete, and not being allowed to use something like boost::shared_ptr. I agree that GC has taken us a step forward in dealing with memory resources, but it seems it was at the cost of not being able to deal with other resources as well. I'm not a compiler expert so I may be way off base. But I think what might be nice is if I can define a class as being reference counted instead of garbage collected. Then any time you create an instance of that class using "new", it would be assigned to a reference-counted reference, not garbage collected. The compiler would keep track of that automatically. The user of the reference counted class wouldn't have to know or remember to dispose it. I know reference counting can have performance penalties, but most of the time in my programs it doesn't matter. I'm not creating thousands of instances or references but only a few. Another problem cited with reference counting is circular references. But let me have the choice. I think I might at least alleviate that problem by using the equivalent to boost's weak pointers. Maybe it's better to think of memory and non-memory resources as different things and handle them differently as opposed to lumping them together using the same mechanism. I'm not sure if there is already a way to deal with this in D as I'm not quite that familiar with D. Jim
Dec 13 2008
parent Christopher Wright <dhasenan gmail.com> writes:
Jim Hewes wrote:
 Maybe it's better to think of memory and non-memory resources as different 
 things and handle them differently as opposed to lumping them together using 
 the same mechanism. I'm not sure if there is already a way to deal with this 
 in D as I'm not quite that familiar with D.

It requires that you design your classes differently. You were pretty much asking, if you don't change your design from something that worked in C++, will it work in D? And it won't, if you use D classes. If you use D structs in D2, you get constructors and destructors for them, and you can use RAII like in C++. In D1 and D2, you can use scope classes (or scope instances). In D, you could use the C# approach, though you don't have a using statement. You can instead use scope: scope foo = new Disposable; // do stuff Or: auto foo = new Disposable; scope (exit) foo.dispose();
Dec 13 2008
prev sibling parent Sean Kelly <sean invisibleduck.org> writes:
== Quote from Jim Hewes (jimhewes gmail.com)'s article
 On that web page he says that "the lack of garbage collection makes C++
 exceptions ... inherently defective." I'm not sure I agree with that. I
 think the opposite is more true. When you're using garbage collection, you
 can't rely on destructors to release resources (notably non-memory
 resources) when exceptions occur. In C# for example, the solution is
 supposed to be the 'using' statment, but that's only useful in the context
 of a function. It doesn't help you when you want class A to be a member of
 another class B and it's lifetime to be governed by the lifetime of class B.

Once upon a time, the 'scope' keyword was going to be extended to cover this case. It still might I suppose. The design of D 2.0 has been focused almost entirely on multiprogramming. Sean
Dec 08 2008