发新话题
打印

C++程序员如何用D编程

C++程序员如何用D编程

知识若不分享 实在没有意义 http://www.d-programming-language-china.org 20070430

点击下面网址查看原文:
http://www.d-programming-language-china.org

by: uFramer D语言论坛 http://www.d-programming-language-china.org
from: http://www.digitalmars.com/d/cpptod.html
version: 基于DMD 1.014

每个有经验的 C++ 程序员都积累了一系列的习惯和技术,这几乎成了第二天性。有时候,当学习一门新语言时,这些习惯会因为太令人舒适而使人看不到新语言中等价的方法。所以下面收集了一些常用的 C++ 技术,以及如何在 D 中完成同样的任务。
另见:C 程序员如何用 D 编程
http://bbs.d-programming-language-china.org/thread-549-1.html

定义构造函数

C++ 的方式
构造函数同类同名:

QUOTE:
class Foo
{
    Foo(int x);
};

D 的方式
构造函数用 this 关键字定义:

class Foo
{
    this(int x) { }
}

which reflects how they are used in D.

基类初始化

C++ 的方式
基类构造函数通过参数初始化列表语法调用。

QUOTE:
class A { A() {... } };
class B : A
{
     B(int x)
    : A()        // 调用基类构造函数
     {    ...
     }
};

D 的方式
基类构造函数通过 super 关键字调用:( 本文出处: http://www.d-programming-language-china.org )

class A { this() { ... } }
class B : A
{
     this(int x)
     {    ...
    super();        // 调用基类构造函数
    ...
     }
}

D 的方式优于 C++ 的地方在于可以灵活的在派生类的构造函数中的任何地方调用基类构造函数。D 还可以让一个构造函数调用另一个构造函数:

class A
{    int a;
    int b;
    this() { a = 7; b = foo(); }
    this(int x)
    {
        this();
        a = x;
    }
}

也可以在调用构造函数之前初始化成员,所以上面的例子等价于:

class A
{    int a = 7;
    int b;
    this() { b = foo(); }
    this(int x)
    {
        this();
        a = x;
    }
}

比较结构

C++ 的方式

尽管 C++ 用简单、便捷的方式定义了结构之间的赋值:

QUOTE:
struct A x, y;
...
x = y;

但这不适用于结构之间的比较。因此,如果要比较两个结构实例之间的等价性的话:( 本文出处: http://www.d-programming-language-china.org )

QUOTE:
#include <string.h>

struct A x, y;

inline bool operator==(const A& x, const A& y)
{
    return (memcmp(&x, &y, sizeof(struct A)) == 0);
}
...
if (x == y)
    ...

注意对于每个需要比较的结构来说,都要进行运算符重载,并且对运算符的重载会抛弃所有的语言提供的类型检查。C++ 的方式还有另一个问题,它不会检查 (x == y) 真正会发生什么,你不得不察看每一个被重载的 operator==() 以确定它们都做了些什么。
如果在 operator==() 中使用 memcmp() 还回造成潜在而丑陋的 bug 。由于对齐的缘故,结构的内存分布不一定是连续的,其中可能会有“洞”。C++ 并不保证这些用于对齐的洞中的值是确定的,所以两个结构实例可能拥有完全相同的结构成员,但是却因为洞中含有不同的垃圾而不相等。

为了解决这个问题,operator==() 可以实现为按成员(memberwise)比较。不幸的是,这是不可靠的,因为 (1) 如果一个成员被加入到结构定义中,程序员可能会忘记同时把它加到 operator==() 中,(2) 对于浮点数的 nan 值来说,就算它们按位比较相等,比较的结果也是不等。

在 C++ 中没有健壮的解决方案。

D 的方式
D 的方式明显而直接:

A x, y;
...
if (x == y)
    ...

创造新的 typedef 类型

C++ 的方式
Typedef 在 C++ 中是弱的,就是说,它们不会真正引入一个新的类型。编译器并不区分 typedef 和它底层的类型。( 本文出处: http://www.d-programming-language-china.org )

QUOTE:
#define HANDLE_INIT    ((Handle)(-1))
typedef void *Handle;
void foo(void *);
void bar(Handle);

Handle h = HANDLE_INIT;
foo(h);            // 未被捕获的编码错误
bar(h);            // ok

C++ 的解决方案是创建一个伪结构,这个结构的唯一的目的就是获得真正的新类型所具有的类型检查和重载能力。

QUOTE:
#define HANDLE_INIT    ((void *)(-1))
struct Handle
{    void *ptr;
    Handle() { ptr = HANDLE_INIT; }        // 默认初始值
    Handle(int i) { ptr = (void *)i; }
    operator void*() { return ptr; }        // 转换为底层的类型
};
void bar(Handle);

Handle h;
bar(h);
h = func();
if (h != HANDLE_INIT)
    ...

D 的方式
不需要上面那种惯用的构造。只需要这样写:

typedef void *Handle = cast(void *)-1;
void bar(Handle);

Handle h;
bar(h);
h = func();
if (h != Handle.init)
    ...

注意,可以给 typedef 提供一个默认的初始值作为新类型的初始值。

Friends友元

C++ 的方式
有时两个类关系很紧密,它们之间不是继承关系,但是它们需要互相访问对方的私有成员。在 C++ 中这样用到 friend 声明:

QUOTE:
class A
{
    private:
    int a;

    public:
    int foo(B *j);
    friend class B;
    friend int abc(A *);
};

class B
{
    private:
    int b;

    public:
    int bar(A *j);
    friend class A;
};

int A::foo(B *j) { return j->b; }
int B::bar(A *j) { return j->a; }

int abc(A *p) { return p->a; }

D 的方式
在 D 中,位于同一个模块的类隐式地具有友元访问权限。这样做是有道理的,因为关系紧密地类应该位于同一个模块中,所以隐式地赋予位于同一个模块中的其他类友元访问权限是优雅的:( 本文出处: http://www.d-programming-language-china.org )

module X;

class A
{
    private:
    static int a;

    public:
    int foo(B j) { return j.b; }
}

class B
{
    private:
    static int b;

    public:
    int bar(A j) { return j.a; }
}

int abc(A p) { return p.a; }

private 特征禁止从其他模块中访问成员。( 本文出处: http://www.d-programming-language-china.org )

运算符重载

C++ 的方式
假设有一个结构代表了一种新的算术类型,将其的运算符重载以使其可以和整数比较是很方便的:

QUOTE:
struct A
{
    int operator <    (int i);
    int operator <= (int i);
    int operator >    (int i);
    int operator >= (int i);
};

int operator <    (int i, A &a) { return a >    i; }
int operator <= (int i, A &a) { return a >= i; }
int operator >    (int i, A &a) { return a <    i; }
int operator >= (int i, A &a) { return a <= i; }

所有的 8 个函数缺一不可。
D 的方式
D 认识到比较运算符在根本上互相之间是有联系的。所以只用一个函数是必需的:

struct A
{
    int opCmp(int i);
}

编译器依照 opCmp 函数自动解释 <、<=、> 和 >= 运算符,并处理左操作数不是对象引用的情况。
类似这样的明智的规则也适用于其他的运算符重载,这就使得 D 中的运算符重载不像在 C++ 中那样繁琐且易于出错。只需要少得多的代码,就可以达到相同的效果。

名字空间 using 声明

C++ 的方式
C++ 中的 using 声明 用来从一个名字空间作用域将名字引入当前的作用域:( 本文出处: http://www.d-programming-language-china.org )

QUOTE:
namespace Foo
{
    int x;
}
using Foo::x;

D 的方式
D 用模块来代替名字空间和 #include 文件,用别名声明来代替 using 声明:

/** Module foo.d **/
module foo;
int x;

/** Another module **/
import foo;
alias foo.x x;

别名比单一目的的using 声明灵活得多。别名可以用来重命名符号,引用模板成员,引用嵌套类类型等。

RAII(资源获得即初始化)

C++ 的方式
在 C++ 中,资源如内存等,都需要显式的处理。因为当退出当前作用域时会自动调用析构函数,RAII 可以通过将资源释放代码放进析构函数中实现:

QUOTE:
class File
{    Handle *h;

    ~File()
    {
    h->release();
    }
};

D 的方式
大多数的资源释放问题都是简单的跟踪并释放内存。在 D 中这是由垃圾收集程序自动完成的。除了内存外,用得最普遍的资源要数信号量和锁了,在 D 中可用 synchronized 声明和语句自动管理。
其余少见的情况可用 scope 类处理。scope 类退出其作用域时,会调用它们的析构函数。

scope class File
{    Handle h;

    ~this()
    {
    h.release();
    }
}

void test()
{
    if (...)
    {    scope f = new File();
    ...
    } // f.~this() gets run at closing brace, even if
        // scope was exited via a thrown exception
}

属性

C++ 的方式
人们常常会定义一个域,同时为它提供面向对象的 get 和 set 函数:( 本文出处: http://www.d-programming-language-china.org )

QUOTE:
class Abc
{
    public:
    void setProperty(int newproperty) { property = newproperty; }
    int getProperty() { return property; }

    private:
    int property;
};

Abc a;
a.setProperty(3);
int x = a.getProperty();

所有这些都不过是增加了击键的次数而已,并且还会使代码变得不易阅读,因为其中充满了 getProperty() 和 setProperty() 调用。
D 的方式
属性可以使用正常的域语法 get 和 set,然后 get 和 set 会被编译器用方法调用取代。

class Abc
{
    // set
    void property(int newproperty) { myprop = newproperty; }

    // get
    int property() { return myprop; }

    private:
    int myprop;
}

使用时:

Abc a;
a.property = 3;        // 等价于 a.property(3)
int x = a.property;    // 等价于 int x = a.property()

因此,在 D 中属性可以被看作一个简单的域名。开始时,属性可以只是一个简单的域名,但是如果后来需要将读取和设置行为改变为函数调用,只需要改动类的定义就够了。这样就避免了定义 get 和 set 时敲入冗长的代码,仅仅是为了‘谨防’日后派生类有可能会不得不重载它们。这也是一种定义接口类的方法,这些类没有数据域,只在语法上表现得好像它们作了实际工作。

递归模板

C++ 的方式
一种使用模板的高级方式是递归的扩展它们,依靠特化来终止递归。用来计算阶乘的模板可能会是这样:

QUOTE:
template<int n> class factorial
{
    public:
    enum { result = n * factorial<n - 1>::result };
};

template<> class factorial<1>
{
    public:
    enum { result = 1 };
};

void test()
{
    printf("%d\n", factorial<4>::result); // 打印出 24
}

D 的方式
D 的版本与之相似,但是简单一点,利用了将单一模板成员提升到外围的名字空间的能力:( 本文出处: http://www.d-programming-language-china.org )

template factorial(int n)
{
    enum { factorial = n * .factorial!(n-1) }
}

template factorial(int n : 1)
{
    enum { factorial = 1 }
}

void test()
{
    printf("%d\n", factorial!(4));    // prints 24
}

Meta Templates

The problem: create a typedef for a signed integral type that is at least nbits in size.
The C++ Way
This example is simplified and adapted from one written by Dr. Carlo Pescio in Template Metaprogramming: Make parameterized integers portable with this novel technique.
There is no way in C++ to do conditional compilation based on the result of an expression based on template parameters, so all control flow follows from pattern matching of the template argument against various explicit template specializations. Even worse, there is no way to do template specializations based on relationships like "less than or equal to", so the example uses a clever technique where the template is recursively expanded, incrementing the template value argument by one each time, until a specialization matches. If there is no match, the result is an unhelpful recursive compiler stack overflow or internal error, or at best a strange syntax error.

A preprocessor macro is also needed to make up for the lack of template typedefs.

QUOTE:
#include <limits.h>

template< int nbits > struct Integer
{
    typedef Integer< nbits + 1 > :: int_type int_type ;
} ;

struct Integer< 8 >
{
    typedef signed char int_type ;
} ;

struct Integer< 16 >
{
    typedef short int_type ;
} ;

struct Integer< 32 >
{
    typedef int int_type ;
} ;

struct Integer< 64 >
{
    typedef long long int_type ;
} ;

// If the required size is not supported, the metaprogram
// will increase the counter until an internal error is
// signaled, or INT_MAX is reached. The INT_MAX
// specialization does not define a int_type, so a
// compiling error is always generated
struct Integer< INT_MAX >
{
} ;

// A bit of syntactic sugar
#define Integer( nbits ) Integer< nbits > :: int_type

#include <stdio.h>

int main()
{
    Integer( 8 ) i ;
    Integer( 16 ) j ;
    Integer( 29 ) k ;
    Integer( 64 ) l ;
    printf("%d %d %d %d\n",
    sizeof(i), sizeof(j), sizeof(k), sizeof(l));
    return 0 ;
}

The C++ Boost Way
This version uses the C++ Boost library. It was provided by David Abrahams.

QUOTE:
#include <boost/mpl/if.hpp>
#include <boost/mpl/assert.hpp>

template <int nbits> struct Integer
    : mpl::if_c<(nbits <= 8), signed char
    , mpl::if_c<(nbits <= 16), short
    , mpl::if_c<(nbits <= 32), long
    , long long>::type >::type >
{
    BOOST_MPL_ASSERT_RELATION(nbits, <=, 64);
}

#include <stdio.h>

int main()
{
    Integer< 8 > i ;
    Integer< 16 > j ;
    Integer< 29 > k ;
    Integer< 64 > l ;
    printf("%d %d %d %d\n",
    sizeof(i), sizeof(j), sizeof(k), sizeof(l));
    return 0 ;
}

The D Way
The D version could also be written with recursive templates, but there's a better way. Unlike the C++ example, this one is fairly easy to figure out what is going on. It compiles quickly, and gives a sensible compile time message if it fails.

template Integer(int nbits)
{
    static if (nbits <= 8)
    alias byte Integer;
    else static if (nbits <= 16)
    alias short Integer;
    else static if (nbits <= 32)
    alias int Integer;
    else static if (nbits <= 64)
    alias long Integer;
    else
    static assert(0);
}

int main()
{
    Integer!(8) i ;
    Integer!(16) j ;
    Integer!(29) k ;
    Integer!(64) l ;
    printf("%d %d %d %d\n",
    i.sizeof, j.sizeof, k.sizeof, l.sizeof);
    return 0;
}

Type Traits

Type traits are another term for being able to find out properties of a type at compile time.
The C++ Way
The following template comes from C++ Templates: The Complete Guide, David Vandevoorde, Nicolai M. Josuttis pg. 353 which determines if the template's argument type is a function:( 本文出处: http://www.d-programming-language-china.org )

QUOTE:
template<typename T> class IsFunctionT
{
    private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename U> static One test(...);
    template<typename U> static Two test(U (*)[1]);
    public:
    enum { Yes = sizeof(IsFunctionT<T>::test<T>(0)) == 1 };
};

void test()
{
    typedef int (fp)(int);

    assert(IsFunctionT<fp>::Yes == 1);
}

This template relies on the SFINAE (Substitution Failure Is Not An Error) principle. Why it works is a fairly advanced template topic.
The D Way
SFINAE (Substitution Failure Is Not An Error) can be done in D without resorting to template argument pattern matching:

template IsFunctionT(T)
{
    static if ( is(T[]) )
    const int IsFunctionT = 0;
    else
    const int IsFunctionT = 1;
}

void test()
{
    typedef int fp(int);

    assert(IsFunctionT!(fp) == 1);
}

The task of discovering if a type is a function doesn't need a template at all, nor does it need the subterfuge of attempting to create the invalid array of functions type. The IsExpression expression can test it directly:

void test()
{
    alias int fp(int);

    assert( is(fp == function) );
}
( lastupdate:20070430 最新文章请访问http://www.d-programming-language-china.org )

关于一大步成功社区:
yidabu提倡在交流中学习,在分享中提高
收集感兴趣的知识,写下心得,通过网络与别人一起分享
理解一点就实践一步,收获什么就分享什么,成功就是这样一点点一步步累积起来的
网络只是一个工具,只有自己身心提高才是实实在在的。d-programming-language-china.org为大家提供一个学习交流各种知识的平台

TOP

发新话题