Re: Class member function calls inside ctor and dtor

2018-01-28 Thread Timon Gehr via Digitalmars-d

On 28.01.2018 02:13, Jonathan M Davis wrote:

Yes, but you can have that problem even without getting inheritance involve.
For instance,

class C
{
 immutable string s;

 this()
 {
 s = foo();
 }

 string foo()
 {
 return s ~ "foo";
 }
}

When foo is called from the constructor, s is null, whereas every time it's
accessed after that, it's "foo", meaning that the first time, foo returns
"foo" and all other times, it returns "foofoo". You can also do

class C
{
 immutable string s;

 this()
 {
 s = s ~ "foo";
 }
}

which surprised me. I thought that the compiler prevented you from using an
immutable variable before it was assigned in the constructor, but it
doesn't. It actually can't if you call any member functions unless it
required that all const and immutable members be initialized before calling
other functions, but it could at least prevent it within the constructor. It
doesn't though.


At some point it will need to, as the current behavior can be used to 
violate type system guarantees.


Re: Class member function calls inside ctor and dtor

2018-01-27 Thread Shachar Shemesh via Digitalmars-d

On 28/01/18 03:13, Jonathan M Davis wrote:

On Saturday, January 27, 2018 19:42:26 Steven Schveighoffer via Digitalmars-
d wrote:

Well, a virtual function may expect that the ctor has been run, and
expect that members are different from their init values.


Yes, but you can have that problem even without getting inheritance involve.


Indeed. D's lack of proper definition of when underlying objects are 
initialized will strike you here as well.


However

Here, at least, you can view the problem locally. The problem is 100% 
contained in the constructor definition, and if it strikes you, you know 
where to look for it.


With the inherited class case, that's not so simple. I can inherit your 
class, and then you can change your class' destructor definition, and 
I'll be caught completely off guard.


A second point is that while the constructor may choose when to call the 
parent's constructor, the destructor has no such prerogative.


Finally, even if you can control when your parent is destroyed, that 
doesn't mean there is anything you can do about it. If your class 
inherently needs a functioning parent in order to do its stuff, then you 
have no choice but to call the parent's super before doing anything else 
in the constructor. If the parent then chooses to call virtual 
functions, you might be facing a problem with no tools to resolve it.


C++'s method of initializing parents is ugly as hell and a little 
confusing, but it is extremely clean and well defined. Both the compiler 
and the programmer know for sure what has been initialized, and no 
accidental calling of or relying on uninitialized members is possible.


Shachar


Re: Class member function calls inside ctor and dtor

2018-01-27 Thread Jonathan M Davis via Digitalmars-d
On Saturday, January 27, 2018 19:42:26 Steven Schveighoffer via Digitalmars-
d wrote:
> Well, a virtual function may expect that the ctor has been run, and
> expect that members are different from their init values.

Yes, but you can have that problem even without getting inheritance involve.
For instance,

class C
{
immutable string s;

this()
{
s = foo();
}

string foo()
{
return s ~ "foo";
}
}

When foo is called from the constructor, s is null, whereas every time it's
accessed after that, it's "foo", meaning that the first time, foo returns
"foo" and all other times, it returns "foofoo". You can also do

class C
{
immutable string s;

this()
{
s = s ~ "foo";
}
}

which surprised me. I thought that the compiler prevented you from using an
immutable variable before it was assigned in the constructor, but it
doesn't. It actually can't if you call any member functions unless it
required that all const and immutable members be initialized before calling
other functions, but it could at least prevent it within the constructor. It
doesn't though.

So, you can do some weird stuff with structs or classes that have been
initialized with their init values but not had all of their constructors
run, but because D initializes the object with the init value first, at
least you get something consistent out of the deal, and there are no
problems with the wrong version of a virtual function being called, because
the object was only partially constructed, whereas in C++, you can end up
crashing the program due to stuff like calling an abstract function that's
only defined in derived classes.

- Jonathan M Davis



Re: Class member function calls inside ctor and dtor

2018-01-27 Thread Steven Schveighoffer via Digitalmars-d

On 1/27/18 12:01 PM, Jonathan M Davis wrote:

On Saturday, January 27, 2018 16:18:26 Thomas Mader via Digitalmars-d wrote:

On Saturday, 27 January 2018 at 14:48:08 UTC, Johan Engelen wrote:

I'm working on devirtualization and for that it's crucial to
know some spec details (or define them in the spec if they
aren't yet).

Currently, calling non-final member functions in the destructor
is allowed and these indirect calls are to the possibly
overridden functions of derived classes. That is, inside a base
class constructor, the object's type is its final type (so
possibly of a derived class). This is the same in the
destructor. Thus, the object's dynamic type does not change
during its lifetime.


Can't answer your question but have a little question.
How is the behavior different to the situation in C++? They argue
that it's not good to call virtual methods in Con-/Destructors in
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-ctor-virtu
al

So I guess it should better be not used in D as well?


D solved that problem. In C++, when you're in the base class constructor,
the object doesn't have its derived type yet. It's still just the base
class. Each class level gets add as it's constructed (the same in reverse
with the destructor). You don't have a full object until all constructors
have been run, and once you start running destructors, you don't have a full
class anymore either.

In D, on the other hand, the object is initialized with its init value
_before_ any constructors are run. So, it's a full object with a full type,
and everything virtual is going to get the type right.


Well, a virtual function may expect that the ctor has been run, and 
expect that members are different from their init values.


However, because you can initialize that data before calling the 
superclass' constructor, you can alleviate this problem as well.


For instance, the invariant may be called when you call the virtual 
function:


import std.stdio;

class A
{
this() { writeln("A.ctor"); foo(); }
void foo() { writeln("A.foo"); }
}

class B : A
{
int x;
this() {
writeln("B.ctor");
x = 1; // note the intialization before calling the base class
super();
}
invariant {
writeln("invariant!");
assert(x == 1);
}
override void foo() { writeln("B.foo"); }
}

void main()
{
auto b = new B;
}

output:
B.ctor
A.ctor
invariant! <- before calling foo
B.foo
invariant! <- after calling foo
invariant! <- after constructors are done

-Steve


Re: Class member function calls inside ctor and dtor

2018-01-27 Thread Jonathan M Davis via Digitalmars-d
On Saturday, January 27, 2018 16:18:26 Thomas Mader via Digitalmars-d wrote:
> On Saturday, 27 January 2018 at 14:48:08 UTC, Johan Engelen wrote:
> > I'm working on devirtualization and for that it's crucial to
> > know some spec details (or define them in the spec if they
> > aren't yet).
> >
> > Currently, calling non-final member functions in the destructor
> > is allowed and these indirect calls are to the possibly
> > overridden functions of derived classes. That is, inside a base
> > class constructor, the object's type is its final type (so
> > possibly of a derived class). This is the same in the
> > destructor. Thus, the object's dynamic type does not change
> > during its lifetime.
>
> Can't answer your question but have a little question.
> How is the behavior different to the situation in C++? They argue
> that it's not good to call virtual methods in Con-/Destructors in
> https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-ctor-virtu
> al
>
> So I guess it should better be not used in D as well?

D solved that problem. In C++, when you're in the base class constructor,
the object doesn't have its derived type yet. It's still just the base
class. Each class level gets add as it's constructed (the same in reverse
with the destructor). You don't have a full object until all constructors
have been run, and once you start running destructors, you don't have a full
class anymore either.

In D, on the other hand, the object is initialized with its init value
_before_ any constructors are run. So, it's a full object with a full type,
and everything virtual is going to get the type right.

- Jonathan M Davis



Re: Class member function calls inside ctor and dtor

2018-01-27 Thread Johan Engelen via Digitalmars-d

On Saturday, 27 January 2018 at 16:18:26 UTC, Thomas Mader wrote:


Can't answer your question but have a little question.
How is the behavior different to the situation in C++?


In C++, the dynamic type of an object changes during construction 
and destruction (e.g. base class ctor calls base class 
implementation of virtual functions).
Because of that, it may be confusing to call virtual functions in 
the ctor/dtor, and people advice against it. In D, the situation 
is much more clear (imo).


- Johan



Re: Class member function calls inside ctor and dtor

2018-01-27 Thread Thomas Mader via Digitalmars-d

On Saturday, 27 January 2018 at 14:48:08 UTC, Johan Engelen wrote:
I'm working on devirtualization and for that it's crucial to 
know some spec details (or define them in the spec if they 
aren't yet).


Currently, calling non-final member functions in the destructor 
is allowed and these indirect calls are to the possibly 
overridden functions of derived classes. That is, inside a base 
class constructor, the object's type is its final type (so 
possibly of a derived class). This is the same in the 
destructor. Thus, the object's dynamic type does not change 
during its lifetime.


Can't answer your question but have a little question.
How is the behavior different to the situation in C++? They argue 
that it's not good to call virtual methods in Con-/Destructors in 
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-ctor-virtual


So I guess it should better be not used in D as well?



Class member function calls inside ctor and dtor

2018-01-27 Thread Johan Engelen via Digitalmars-d
I'm working on devirtualization and for that it's crucial to know 
some spec details (or define them in the spec if they aren't yet).


Currently, calling non-final member functions in the destructor 
is allowed and these indirect calls are to the possibly 
overridden functions of derived classes. That is, inside a base 
class constructor, the object's type is its final type (so 
possibly of a derived class). This is the same in the destructor. 
Thus, the object's dynamic type does not change during its 
lifetime.
This greatly simplifies devirtualization, and I want to verify 
that it is in the spec (I can't find it).


See this example program:
```
char glob;

class A {
char c;

this() { c = getType(); }
~this() { glob = getType(); }

char getType() { return 'A'; }
}

class B : A {
override char getType() { return 'B'; }
}

void main() {
{
scope b = new B();
assert(b.c == 'B');
}
assert(glob == 'B');
}
```

My question: where can I find this in the spec, and where in the 
testsuite is this tested?


If it's not in the spec and/or not in the testsuite, I'll add it.

Thanks,
  Johan