为什么C ++中的void方法可以返回void值,但在其他语言中它不能?

时间:2016-04-04 13:57:46

标签: java c# c++ void

这个程序在C ++中编译和运行,但是没有多种语言,比如Java和C#。

#include <iostream>
using namespace std;

void foo2() {
  cout << "foo 2.\n";
}

void foo() {
    return foo2();
}

int main() {

    foo();

    return 0;
}

在Java中,这给出了编译器错误,例如&#39; Void方法不能返回值&#39;。但由于被调用的方法本身就是一个空白,因此它不会返回一个值。我知道为了便于阅读,可能会禁止这样的结构。还有其他异议吗?

编辑:为了将来参考,我在这里发现了一些类似的问题return-void-type-in-c-and-c 以我的拙见,这个问题还没有得到解答。 回复&#39;因为它在规范中这样说,所以继续&#39;并没有削减它,因为有人必须首先编写规范。也许我应该问过&#39;允许返回像C ++这样的无效类型有什么优缺点?

2 个答案:

答案 0 :(得分:20)

这是因为它可能在模板中使用。 C#和Java禁止void作为类型参数,但C ++允许它允许您编写如下模板代码:

template<typename T, typename TResult>
TResult foo(T x, T y)
{
    return foo2(x, y);
}

如果void方法不允许返回void表达式,则TResultvoid时,无法进行此模板实例化。如果是这种情况,如果您希望TResult实际上是void,则需要单独的模板定义。

例如,请记住在C#中有两组通用通用委托,即Func<>Action<>?好吧,Action<T>正好因为Func<T, void>被禁止而存在。 C ++设计人员并不想在可能的情况下引入这样的情况,所以他们决定允许你使用void作为模板参数 - 你找到的案例就是一个促进这一点的功能。 / p>

(请允许我以假装Q&amp; A格式写下其余部分。)

  

但为什么C#和Java 允许类似的构造?

首先,要了解如何使用这些语言实现泛型编程:

  

为什么选择一种实现通用编程的方法呢?

  • 泛型方法维护其余语言的nominal typing。这样做的好处是允许(AOT)编译器进行静态分析,类型检查,错误报告,重载解析以及最终代码生成一次
  • 模板方法基本上是duck typing。用名义上输入的语言打字的鸭子没有上述优点,但它允许你在允许潜在的“无效”的意义上更灵活。事物(&#34;无效&#34;从名义类型系统的角度来看),只要你在程序中的任何地方都没有提到这些无效的可能性。换句话说,模板允许您表达更多的案例统一
  

好的,那么C#和Java需要做些什么来支持void作为有效的泛型参数呢?

我不得不推测回答这个问题,但我会尝试。

在语言层面,他们必须放弃return;仅在void方法中有效并且对于非void方法始终无效的概念。如果没有这种改变,很少有用的方法可以被实例化 - 并且它们都可能必须以递归或无条件throwwhich satisfies both void and non-void methods without returning)结束。所以为了使它变得有用,C#和Java还必须引入允许你返回void表达式的C ++特性。

好的,我们假设您已经拥有了,现在您可以编写如下代码:

void Foo2() { }
void Foo()
{
    return Foo2();
}

同样,非通用版本在C#和Java中与在C ++中一样无用。但让我们继续前进,看看它的真正实用性,这是仿制药。

您现在应该可以编写这样的通用代码了 - TResult现在可以是void(除了已经允许的所有其他类型):

TResult Foo<T, TResult>(T a)
{
    return Foo2(a);
}

但请记住,在C#和Java中,重载解析发生在&#34;早期&#34;而不是&#34;迟到&#34;。对于每个可能的TResult,重载决策算法将选择相同的被调用者。类型检查器将 进行投诉,因为您要么从非void方法返回void表达式,要么返回来自可能void方法的非void表达式。

换句话说,外部方法可以是通用的,除非

  1. 被调用者也是泛型,其返回类型由与外部方法匹配的泛型类型参数定义。
  2. 推迟使用泛型类型和方法的重载分辨率,直到实际的类型参数可用,这样我们就可以在调用点选择正确的非泛型方法。
  3.   

    如果我们使用第一个选项怎么办 - 让被调用者的返回类型变为通用并继续前进?

    我们可以这样做,但它只是将我们的问题推送给被叫方。

    在某些时候,我们需要一些方法来实例化&#34;某种void实例,并且可选择以某种方式接收它。所以现在我们需要void的构造函数(尽管每个void方法可以算作工厂方法,如果你斜视)我们还需要void类型的变量,来自{的可能转换{1}}至void,依此类推。

    基本上,object必须成为所有意图和目的的常规类型(例如a regular empty struct)。这种影响并不可怕,但我认为你可以看出为什么C#和Java会避免它。

      

    第二个选项怎么样 - 推迟重载决议?

    也完全有可能,但请注意,它会有效地将泛型转换为较弱的模板。 (&#34;较弱&#34;在C++ templates aren't restricted to typenames的意义上。)

    同样,它不会成为世界末日,但它会失去我之前描述的泛型的优势。 C#和Java的设计者显然希望保持这些优势。

    旁注:

    在C#中,我知道有一个特殊情况,在验证通用类型定义之后发生绑定。如果void上有new()约束,并且您尝试instantiate a new T(),编译器将生成检查T是否为值类型的代码。然后:

    这个特殊情况非常特殊,因为即使它已经完全推迟了将方法绑定到运行时,编译器仍然可以执行静态分析,类型检查和代码生成一次。毕竟,表达式Activator.CreateInstance的类型始终是new T(),并且可以轻松地解析和验证对具有空形式参数列表的事物的调用。

答案 1 :(得分:4)

根据Java Language Specification §14.17

  

不包含Expression的return语句必须包含在下列之一中,否则会发生编译时错误:

     
      
  • 使用关键字void声明的方法,不返回值(第8.4.5节)
  •   
     

...

     

带有Expression的return语句必须包含在下列之一中,否则会发生编译时错误:

     
      
  • 声明为返回值的方法
  •   
     

...

因此,通过声明方法为void,您说它不返回任何值,因此您只能使用不带表达式的return;语句。