编写像
这样的代码struct S
{
this() // compile-time error
{
}
}
给我一条错误信息
default constructor for structs only allowed with @disable and no body.
为什么?
答案 0 :(得分:13)
这是一个比人们最初期望的更棘手的案例。
D对C ++的一个重要且有用的功能是每个类型(包括所有用户类型)都有一些初始的非垃圾值,可以在编译时进行评估。它用作T.init
,有两个重要的用例:
模板约束可以使用T.init值来检查是否可以在给定类型上执行某些操作(引用Kenji Hara的代码段):
template isSomething(T) {
enum isSomething = is(typeof({
//T t1; // not good if T is nested struct, or has @disable this()
//T t2 = void; auto x = t2; // not good if T is non-mutable type
T t = T.init; // avoid default construct check
...use t...
}));
}
除非明确使用int i = void
语法,否则始终会正确初始化变量。没有可能的垃圾。
鉴于此,出现了一个棘手的问题。我们应该保证T()和T.init是相同的(来自C ++的许多程序员会期望)或允许默认构造可能很容易破坏该保证。据我所知,尽管令人惊讶,但第一种方法的决定更安全。
然而,讨论不断提出各种改进(例如,允许CTFE能够使用默认构造函数)。最近出现了一个such thread。
答案 1 :(得分:9)
它源于D中的所有类型必须具有默认值的事实。有很多地方使用类型的init
值,包括默认初始化成员变量和默认初始化数组中的每个值,并且init
需要在编译了一些这样的案例。让init
提供了很多好处,但它确实妨碍了使用默认构造函数。
真正的默认构造函数需要在所有使用init
的地方使用(或者它不是默认值),但允许任意代码在{{}}的许多情况下运行{使用{1}}充其量只会有问题。至少,您可能被迫使其成为CTFE,并且可能init
。一旦你开始对它施加这样的限制,很快你也可以直接将所有成员变量初始化为你想要的(这是pure
发生的事情),因为你不会获得了很多(如果有的话),这会使默认构造函数变得毫无用处。
有可能同时拥有init
和默认构造函数,但随后会出现关于何时使用另一个构造函数的问题,并且默认构造函数不再是默认构造函数。更不用说,开发人员在使用init
值以及何时使用默认构造函数时可能会变得非常困惑。
现在,我们做能够init
结构的@disable
值(导致它自己的一组问题),在这种情况下,它会是在任何需要init
的情况下使用该结构是非法的。因此,有可能有一个默认的构造函数,它可以在运行时运行任意代码,但具体的后果是什么,我不知道。但是,我确信在某些情况下,人们会希望拥有一个需要init
的默认构造函数,因此无法工作,因为它已经是@disabled(声明类型的数组可能会是其中之一)。
因此,正如您所看到的那样,通过D对init
所做的事情,它使得默认构造函数的整个问题比其他语言更加复杂和有问题。
获得类似于默认构造的正常方法是使用静态init
。像
opCall
然后,只要您使用struct S
{
static S opCall()
{
//Create S with the values that you want and return it.
}
}
- 例如
S()
然后调用静态auto s = S();
,并获得在运行时创建的值。但是,opCall
仍将用于之前的任何地方(包括S.init
),静态S s;
仅在明确使用opCall
时使用。但是,如果你将它与S()
(禁用@disable this()
属性)结合起来,那么你得到的东西类似于我之前描述的那些我们可能拥有@disabled init
的默认构造函数。< / p>
我们可能会或者可能不会最终将默认构造函数添加到该语言中,但是由于init
和语言的工作原因而添加它们存在许多技术问题,而Walter Bright没有认为应该添加它们。因此,对于要添加到语言中的默认构造函数,有人必须提出一个非常引人注目的设计,以适当地解决所有问题(包括令人信服的Walter),我不希望这发生,但我们会见。