Last update Sat Dec 9 00:40:27 2006
Functions
FunctionBody:
BlockStatement
BodyStatement
InStatement BodyStatement
OutStatement BodyStatement
InStatement OutStatement BodyStatement
OutStatement InStatement BodyStatement
InStatement:
in BlockStatement
OutStatement:
out BlockStatement
out ( Identifier ) BlockStatement
BodyStatement:
body BlockStatement
Virtual Functions
Virtual functions are functions that are called indirectly
through a function
pointer table, called a vtbl[], rather than directly.
All non-static non-private non-template member functions are virtual.
This may sound
inefficient, but since the D compiler knows all of the class
hierarchy when generating code, all
functions that are not overridden can be optimized to be non-virtual.
In fact, since
C++ programmers tend to "when in doubt, make it virtual", the D way of
"make it
virtual unless we can prove it can be made non-virtual" results, on
average, in many
more direct function calls. It also results in fewer bugs caused by
not declaring
a function virtual that gets overridden.
Functions with non-D linkage cannot be virtual, and hence cannot be
overridden.
Member template functions cannot be virtual, and hence cannot be
overridden.
Functions marked as final
may not be overridden in a
derived class, unless they are also private
.
For example:
class A
{
int def() { ... }
final int foo() { ... }
final private int bar() { ... }
private int abc() { ... }
}
class B : A
{
int def() { ... } int foo() { ... } int bar() { ... } int abc() { ... } }
void test(A a)
{
a.def(); a.foo(); a.bar(); a.abc(); }
void func()
{ B b = new B();
test(b);
}
Covariant return types
are supported, which means that the
overriding function in a derived class can return a type
that is derived from the type returned by the overridden function:
class A { }
class B : A { }
class Foo
{
A test() { return null; }
}
class Bar : Foo
{
B test() { return null; } }
Function Inheritance and Overriding
A functions in a derived class with the same name and parameter
types as a function in a base class overrides that function:
class A
{
int foo(int x) { ... }
}
class B : A
{
override int foo(int x) { ... }
}
void test()
{
B b = new B();
bar(b);
}
void bar(A a)
{
a.foo(1); }
However, when doing overload resolution, the functions in the base
class are not considered:
class A
{
int foo(int x) { ... }
int foo(long y) { ... }
}
class B : A
{
override int foo(long x) { ... }
}
void test()
{
B b = new B();
b.foo(1); A a = b;
a.foo(1); }
To consider the base class's functions in the overload resolution
process, use an
AliasDeclaration:
class A
{
int foo(int x) { ... }
int foo(long y) { ... }
}
class B : A
{
alias A.foo foo;
override int foo(long x) { ... }
}
void test()
{
B b = new B();
bar(b);
}
void bar(A a)
{
a.foo(1); B b = new B();
b.foo(1); }
A function parameter's default value is not inherited:
class A
{
void foo(int x = 5) { ... }
}
class B : A
{
void foo(int x = 7) { ... }
}
class C : B
{
void foo(int x) { ... }
}
void test()
{
A a = new A();
a.foo();
B b = new B();
b.foo();
C c = new C();
c.foo(); }
Inline Functions
There is no inline keyword. The compiler makes the decision whether to
inline a function or not, analogously to the register keyword no
longer being relevant to a
compiler's decisions on enregistering variables.
(There is no register keyword either.)
Function Overloading
In C++, there are many complex levels of function overloading, with
some defined as "better" matches than others. If the code designer
takes advantage of the more subtle
behaviors of overload function selection, the code can become
difficult to maintain. Not
only will it take a C++ expert to understand why one function is
selected over another, but different C++ compilers can implement
this tricky feature differently, producing
subtly disastrous results.
In D, function overloading is simple. It matches exactly, it matches with
implicit conversions, or it does not match. If there is more than one match, it is an error.
Functions defined with non-D linkage cannot be overloaded.
Function Parameters
Parameters are in, out, inout or lazy.
in is the default; the others work like
storage classes. For example:
int foo(int x, out int y, inout int z, int q);
x is
in, y is
out, z is
inout, and q is
in.
out is rare enough, and inout even rarer, to
attach the keywords to
them and leave in as
the default. The reasons to have them are:
- The function declaration makes it clear what the inputs and
outputs to the function
are.
- It eliminates the need for IDL as a separate language.
- It provides more information to the compiler, enabling more
error checking and
possibly better code generation.
- It (perhaps?) eliminates the need for reference (&) declarations.
out parameters are set to the default initializer for the
type of it. For example:
void foo(out int x)
{
}
int a = 3;
foo(a);
void abc(out int x)
{
x = 2;
}
int y = 3;
abc(y);
void def(inout int x)
{
x += 1;
}
int z = 3;
def(z);
For dynamic array and object parameters, which are passed
by reference, in/out/inout
apply only to the reference and not the contents.
Lazy arguments are evaluated not when the function is called,
but when the parameter is evaluated within the function. Hence,
a lazy argument can be executed 0 or more times. A lazy parameter
cannot be an lvalue.
void dotimes(int n, lazy void exp)
{
while (n--)
exp();
}
void test()
{ int x;
dotimes(3, writefln(x++));
}
prints to the console:
0
1
2
A lazy parameter of type void
can accept an argument
of any type.
Variadic Functions
Functions taking a variable number of arguments are called
variadic functions. A variadic function can take one of
three forms:
- C-style variadic functions
- Variadic functions with type info
- Typesafe variadic functions
C-style Variadic Functions
A C-style variadic function is declared as taking
a parameter of ... after the required function parameters.
It has non-D linkage, such as
extern (C)
:
extern (C) int foo(int x, int y, ...);
foo(3, 4); foo(3, 4, 6.8); foo(2);
There must be at least one non-variadic parameter declared.
extern (C) int def(...);
C-style variadic functions match the C calling convention for
variadic functions, and is most useful for calling C library
functions like
printf
.
The implementiations of these variadic functions have a special
local variable declared for them,
_argptr, which is a
void*
pointer to the first of the
variadic
arguments. To access the arguments,
_argptr must be cast
to a pointer to the expected argument type:
foo(3, 4, 5);
int foo(int x, int y, ...)
{ int z;
z = *cast(int*)_argptr; }
To protect against the vagaries of stack layouts on different
CPU architectures, use
std.c.stdarg to access the variadic
arguments:
import std.c.stdarg;
D-style Variadic Functions
Variadic functions with argument and type info are declared as taking
a parameter of ... after the required function parameters.
It has D linkage, and need not have any non-variadic parameters
declared:
int abc(char c, ...); int def(...);
These variadic functions have a special local variable declared for
them,
_argptr, which is a
void*
pointer to the first of the
variadic
arguments. To access the arguments,
_argptr must be cast
to a pointer to the expected argument type:
foo(3, 4, 5);
int foo(int x, int y, ...)
{ int z;
z = *cast(int*)_argptr; }
An additional hidden argument
with the name
_arguments
and type TypeInfo[]
is passed to the function.
_arguments gives the number of arguments and the type
of each, enabling the creation of typesafe variadic functions.
class Foo { int x = 3; }
class Bar { long y = 4; }
void printargs(int x, ...)
{
printf("%d arguments\n", _arguments.length);
for (int i = 0; i < _arguments.length; i++)
{ _arguments[i].print();
if (_arguments[i] == typeid(int))
{
int j = *cast(int *)_argptr;
_argptr += int.sizeof;
printf("\t%d\n", j);
}
else if (_arguments[i] == typeid(long))
{
long j = *cast(long *)_argptr;
_argptr += long.sizeof;
printf("\t%lld\n", j);
}
else if (_arguments[i] == typeid(double))
{
double d = *cast(double *)_argptr;
_argptr += double.sizeof;
printf("\t%g\n", d);
}
else if (_arguments[i] == typeid(Foo))
{
Foo f = *cast(Foo*)_argptr;
_argptr += Foo.sizeof;
printf("\t%p\n", f);
}
else if (_arguments[i] == typeid(Bar))
{
Bar b = *cast(Bar*)_argptr;
_argptr += Bar.sizeof;
printf("\t%p\n", b);
}
else
assert(0);
}
}
void main()
{
Foo f = new Foo();
Bar b = new Bar();
printf("%p\n", f);
printargs(1, 2, 3L, 4.5, f, b);
}
which prints:
00870FE0
5 arguments
int
2
long
3
double
4.5
Foo
00870FE0
Bar
00870FD0
To protect against the vagaries of stack layouts on different
CPU architectures, use std.stdarg to access the variadic
arguments:
import std.stdarg;
void foo(int x, ...)
{
printf("%d arguments\n", _arguments.length);
for (int i = 0; i < _arguments.length; i++)
{ _arguments[i].print();
if (_arguments[i] == typeid(int))
{
int j = va_arg!(int)(_argptr);
printf("\t%d\n", j);
}
else if (_arguments[i] == typeid(long))
{
long j = va_arg!(long)(_argptr);
printf("\t%lld\n", j);
}
else if (_arguments[i] == typeid(double))
{
double d = va_arg!(double)(_argptr);
printf("\t%g\n", d);
}
else if (_arguments[i] == typeid(FOO))
{
FOO f = va_arg!(FOO)(_argptr);
printf("\t%p\n", f);
}
else
assert(0);
}
}
Typesafe Variadic Functions
Typesafe variadic functions are used when the variable argument
portion of the arguments are used to construct an array or
class object.
For arrays:
int test()
{
return sum(1, 2, 3) + sum(); }
int func()
{
int[3] ii = [4, 5, 6];
return sum(ii); }
int sum(int[] ar ...)
{
int s;
foreach (int x; ar)
s += x;
return s;
}
For static arrays:
int test()
{
return sum(2, 3); return sum(1, 2, 3); }
int func()
{
int[3] ii = [4, 5, 6];
int[] jj = ii;
return sum(ii); return sum(jj); }
int sum(int[3] ar ...)
{
int s;
foreach (int x; ar)
s += x;
return s;
}
For class objects:
class Foo
{
int x;
char[] s;
this(int x, char[] s)
{
this.x = x;
this.s = s;
}
}
void test(int x, Foo f ...);
...
Foo g = new Foo(3, "abc");
test(1, g); test(1, 4, "def"); test(1, 5);
An implementation may construct the object or array instance
on the stack. Therefore, it is an error to refer to that
instance after the variadic function has returned:
Foo test(Foo f ...)
{
return f; }
int[] test(int[] a ...)
{
return a; return a[0..1]; return a.dup; }
For other types, the argument is built with itself, as in:
int test(int i ...)
{
return i;
}
...
test(3); test(3, 4); int[] x;
test(x);
Lazy Variadic Functions
If the variadic parameter is an array of delegates
with no parameters:
void foo(int delegate()[] dgs ...);
Then each of the arguments whose type does not match that
of the delegate is converted to a delegate.
int delegate[] dg;
foo(1, 3+x, dg, cast(int delegate[])null);
is the same as:
foo( { return 1; }, { return 3+x; }, dg, null );
Local Variables
It is an error to use a local variable without first assigning it a
value. The implementation may not always be able to detect these
cases. Other language compilers sometimes issue a warning for this,
but since it is always a bug, it should be an error.
It is an error to declare a local variable that is never referred to.
Dead variables, like anachronistic dead code, are just a source of
confusion for maintenance programmers.
It is an error to declare a local variable that hides another local
variable in the same function:
void func(int x)
{ int x; error, hides previous definition of x
double y;
...
{ char y; error, hides previous definition of y
int z;
}
{ wchar z; legal, previous z is out of scope
}
}
While this might look unreasonable, in practice whenever
this is done it either is a
bug or at least looks like a bug.
It is an error to return the address of or a reference to a
local variable.
It is an error to have a local variable and a label with the same name.
Functions may be nested within other functions:
int bar(int a)
{
int foo(int b)
{
int abc() { return 1; }
return b + abc();
}
return foo(a);
}
void test()
{
int i = bar(3); }
Nested functions can be accessed only if the name is in scope.
void foo()
{
void A()
{
B(); C(); }
void B()
{
A(); void C()
{
void D()
{
A(); B(); C(); D(); }
}
}
A(); B(); C(); }
and:
int bar(int a)
{
int foo(int b) { return b + 1; }
int abc(int b) { return foo(b); } return foo(a);
}
void test()
{
int i = bar(3); int j = bar.foo(3); }
Nested functions have access to the variables and other symbols
defined by the lexically enclosing function.
This access includes both the ability to read and write them.
int bar(int a)
{ int c = 3;
int foo(int b)
{
b += c; c++; return b + c; }
c = 4;
int i = foo(a); return i + c; }
void test()
{
int i = bar(3); }
This access can span multiple nesting levels:
int bar(int a)
{ int c = 3;
int foo(int b)
{
int abc()
{
return c; }
return b + c + abc();
}
return foo(3);
}
Static nested functions cannot access any stack variables of
any lexically enclosing function, but can access static variables.
This is analogous to how static member functions behave.
int bar(int a)
{ int c;
static int d;
static int foo(int b)
{
b = d; b = c; return b + 1;
}
return foo(a);
}
Functions can be nested within member functions:
struct Foo
{ int a;
int bar()
{ int c;
int foo()
{
return c + a;
}
return 0;
}
}
Member functions of nested classes and structs do not have
access to the stack variables of the enclosing function, but
do have access to the other symbols:
void test()
{ int j;
static int s;
struct Foo
{ int a;
int bar()
{ int c = s; int d = j;
int foo()
{
int e = s; int f = j; return c + a; }
return 0;
}
}
}
Nested functions always have the D function linkage type.
Unlike module level declarations, declarations within function
scope are processed in order. This means that two nested functions
cannot mutually call each other:
void test()
{
void foo() { bar(); } void bar() { foo(); } }
The solution is to use a delegate:
void test()
{
void delegate() fp;
void foo() { fp(); }
void bar() { foo(); }
fp = &bar;
}
Future directions: This restriction may be removed.
Delegates, Function Pointers, and Dynamic Closures
A function pointer can point to a static nested function:
int function() fp;
void test()
{ static int a = 7;
static int foo() { return a + 3; }
fp = &foo;
}
void bar()
{
test();
int i = fp(); }
A delegate can be set to a non-static nested function:
int delegate() dg;
void test()
{ int a = 7;
int foo() { return a + 3; }
dg = &foo;
int i = dg(); }
The stack variables, however, are not valid once the function
declaring them has exited, in the same manner that pointers to
stack variables are not valid upon exit from a function:
int* bar()
{ int b;
test();
int i = dg(); return &b; }
Delegates to non-static nested functions contain two pieces of
data: the pointer to the stack frame of the lexically enclosing
function (called the frame pointer) and the address of the
function. This is analogous to struct/class non-static member
function delegates consisting of a this pointer and
the address of the member function.
Both forms of delegates are interchangeable, and are actually
the same type:
struct Foo
{ int a = 7;
int bar() { return a; }
}
int foo(int delegate() dg)
{
return dg() + 1;
}
void test()
{
int x = 27;
int abc() { return x; }
Foo f;
int i;
i = foo(&abc); i = foo(&f.bar); }
This combining of the environment and the function is called
a dynamic closure.
The .ptr property of a delegate will return the
frame pointer value as a void*
.
The .funcptr property of a delegate will return the
function pointer value as a function type.
Future directions: Function pointers and delegates may merge
into a common syntax and be interchangeable with each other.
Anonymous Functions and Anonymous Delegates
See Function Literals.
main() Function
For console programs, main()
serves as the entry point.
It gets called after all the module initializers are run, and
after any unittests are run.
After it returns, all the module destructors are run.
main()
must be declared using one of the following forms:
void main() { ... }
void main(char[][] args) { ... }
int main() { ... }
int main(char[][] args) { ... }
)
Copyright © 1999-2006 by Digital Mars, All Rights Reserved
|
Page generated by
Ddoc. |
Comments