Go的多值返回语句是异常的替代品吗?

huangapple go评论110阅读模式
英文:

Is Go's multi-value return statement an alternative to exceptions?

问题

我想让你做我的中文翻译,代码部分不要翻译, 只返回翻译好的部分, 不要有别的内容。以下是要翻译的内容:

我可以帮你翻译这些内容。以下是翻译好的部分:

对我来说,谷歌对异常的替代方案有以下几种:

  • Go语言:多值返回“return val, err;”
  • Go语言、C++:nil检查(提前返回)
  • Go语言、C++:“处理该错误”(我的术语)
  • C++:assert(expression)

Go语言:defer/panic/recover是在提问之后添加的语言特性

多值返回是否足够有用作为一种替代方案?为什么“asserts”被认为是替代方案?谷歌是否认为如果发生未正确处理的错误,程序停止运行是可以接受的?

Effective Go: 多返回值

> Go语言的一个不寻常的特性是函数和方法可以返回多个值。这可以用来改进C程序中一些笨拙的习惯用法:带有内部错误返回值(例如EOF的-1)和修改参数。
>
> 在C中,写入错误通过负数计数和错误代码隐藏在易失性位置来表示。在Go中,Write函数可以返回计数和错误:“是的,你写了一些字节,但没有写完因为设备已满”。在os包中,*File.Write的签名是:
>
> func (file *File) Write(b []byte) (n int, err Error)
>
> 正如文档所说,当n != len(b)时,它返回写入的字节数和非nil的错误。这是一种常见的风格;更多示例请参见错误处理部分。

[Effective Go: 命名返回参数][2]

> Go函数的返回值或结果“参数”可以被赋予名称,并且可以像普通变量一样使用,就像传入的参数一样。当命名时,它们在函数开始时被初始化为其类型的零值;如果函数执行一个没有参数的返回语句,结果参数的当前值将被用作返回值。
>
> 名称不是强制性的,但它们可以使代码更简洁和清晰:它们是文档。如果我们给nextInt的结果命名,很明显返回的int是哪个。

> func nextInt(b []byte, pos int) (value, nextPos int) {

> 因为命名结果被初始化并与无修饰的返回绑定,它们可以简化和澄清。这是一个使用它们的好例子:

func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
  for len(buf) > 0 && err == nil {
	var nr int;
	nr, err = r.Read(buf);
	n += nr;
	buf = buf[nr:len(buf)];
  }
  return;
}

[为什么Go没有异常?][3]

> 异常也是一个类似的故事。已经提出了许多异常设计,但每个设计都给语言和运行时增加了相当的复杂性。由于它们的本质,异常跨越函数,甚至可能跨越goroutine;它们具有广泛的影响。还有对它们对库的影响的担忧。它们在定义上是异常的,但是对它们的支持会对库和接口规范产生深远影响。如果能找到一种设计,使它们真正成为异常而不鼓励常见错误变成特殊的控制流,需要每个程序员进行补偿,那将是很好的。

> 像泛型一样,异常仍然是一个未解决的问题。

[Google C++编码规范:异常][4]

> 决策:
>
> 从表面上看,使用异常的好处超过了成本,特别是对于新项目而言。然而,对于现有代码,引入异常对所有相关代码都有影响。如果异常可以传播到新项目之外,将新项目集成到现有的无异常代码中也会成为问题。由于谷歌现有的C++代码不具备处理异常的能力,使用会生成异常的新代码相对困难。
>
> 鉴于谷歌现有的代码不具备处理异常的能力,使用异常的成本要比在新项目中使用异常要高一些。转换过程将会很慢且容易出错。我们不认为“可用的异常替代方案,如错误码和断言”会带来重大负担。
>
> 我们反对使用异常的建议并不是基于哲学或道德的原因,而是基于实际的原因。因为我们希望在谷歌使用我们的开源项目,如果这些项目使用异常,那么在谷歌的开源项目中也需要反对使用异常。如果我们从头开始重新做,情况可能会有所不同。

[Go语言:延迟、恐慌和恢复][5]

> 延迟语句允许我们在打开文件后立即考虑关闭每个文件,确保无论函数中有多少个返回语句,文件都将被关闭。

> 延迟语句的行为是直接和可预测的。有三个简单的规则:

> 1. 延迟函数的参数在延迟语句被执行时被求值。

> 在这个例子中,表达式“i”在Println调用被延迟时被求值。延迟调用将在函数返回后打印“0”。

>
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
>
>
> 2. 延迟函数调用在包围函数返回后以后进先出的顺序执行。 这个函数打印“3210”:

>
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
>
>
> 3. 延迟函数可以读取和赋值给返回函数的命名返回值。

> 在这个例子中,延迟函数在包围函数返回后将返回值i递增。因此,这个函数返回2:

>
func c() (i int) {
defer func() { i++ }()
return 1
}
>
> 这对于修改函数的错误返回值很方便;我们很快会看到一个例子。

> 恐慌是一个内置函数,它停止普通的控制流并开始恐慌。 当函数F调用panic时,F的执行停止,F中的任何延迟函数都会正常执行,然后F返回给其调用者。对于调用者来说,F的行为就像调用panic一样。该过程在堆栈上继续,直到当前goroutine中的所有函数都返回,此时程序崩溃。恐慌可以通过直接调用panic来引发。它们也可以由运行时错误引起,例如越界访问数组。

> 恢复是一个内置函数,用于恢复恐慌的控制权。 recover只在延迟函数内部有用。在正常执行期间,调用recover将返回nil并且没有其他效果。如果当前goroutine正在恐慌,调用recover将捕获给panic传递的值并恢复正常执行

> 这是一个演示panic和defer机制的示例程序:

>
> <snip>
>
> 有关panic和recover的实际示例,请参见Go标准库中的json包。它使用一组递归函数解码JSON编码的数据。当遇到格式错误的JSON时,解析器调用panic来展开堆栈到顶级函数调用,然后从panic中恢复并返回适当的错误值(请参见decode.go中的“error”和“unmarshal”函数)。在regexp包的Compile例程中也有类似的示例。Go库的约定是,即使包内部使用panic,其外部API仍然提供明确的错误返回值。

> 延迟的其他用途(除了之前给出的file.Close()示例)包括释放互斥锁:

>
> mu.Lock()
> defer mu.Unlock

英文:

It seems to me Google's alternatives to exceptions are

  • Go: multi-value return "return val, err;"

  • Go, C++: nil checks (early return)

  • Go, C++: "handle the damn error" (my term)

  • C++: assert(expression)

  • Go: defer/panic/recover are language features added after this question was asked

Is multi-value return useful enough to act as an alternative? Why are "asserts" considered alternatives? Does Google think it O.K. if a program halts if an error occurs that is not handled correctly?

Effective Go: Multiple return values

> One of Go's unusual features is that functions and methods can return multiple values. This can be used to improve on a couple of clumsy idioms in C programs: in-band error returns (such as -1 for EOF) and modifying an argument.
>
> In C, a write error is signaled by a
> negative count with the error code
> secreted away in a volatile location.
> In Go, Write can return a count and an
> error: “Yes, you wrote some bytes but
> not all of them because you filled the
> device”. The signature of *File.Write
> in package os is:
>
> func (file *File) Write(b []byte) (n int, err Error)
>
> and as the documentation says, it
> returns the number of bytes written
> and a non-nil Error when n != len(b).
> This is a common style; see the
> section on error handling for more
> examples.

[Effective Go: Named result parameters][2]

> The return or result "parameters" of a
> Go function can be given names and
> used as regular variables, just like
> the incoming parameters. When named,
> they are initialized to the zero
> values for their types when the
> function begins; if the function
> executes a return statement with no
> arguments, the current values of the
> result parameters are used as the
> returned values.
>
> The names are not mandatory but they
> can make code shorter and clearer:
> they're documentation. If we name the
> results of nextInt it becomes obvious
> which returned int is which.
>
> func nextInt(b []byte, pos int) (value, nextPos int) {

> Because named results are initialized and tied to an
> unadorned return, they can simplify as
> well as clarify. Here's a version of
> io.ReadFull that uses them well:

func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
  for len(buf) &gt; 0 &amp;&amp; err == nil {
	var nr int;
	nr, err = r.Read(buf);
	n += nr;
	buf = buf[nr:len(buf)];
  }
  return;
}

[Why does Go not have exceptions?][3]

> Exceptions are a similar story. A number of designs for exceptions have been proposed but each adds significant complexity to the language and run-time. By their very nature, exceptions span functions and perhaps even goroutines; they have wide-ranging implications. There is also concern about the effect they would have on the libraries. They are, by definition, exceptional yet experience with other languages that support them show they have profound effect on library and interface specification. It would be nice to find a design that allows them to be truly exceptional without encouraging common errors to turn into special control flow that requires every programmer to compensate.

>Like generics, exceptions remain an open issue.

[Google C++ Style Guide: Exceptions][4]

> Decision:
>
> On their face, the benefits of using
> exceptions outweigh the costs,
> especially in new projects. However,
> for existing code, the introduction of
> exceptions has implications on all
> dependent code. If exceptions can be
> propagated beyond a new project, it
> also becomes problematic to integrate
> the new project into existing
> exception-free code. Because most
> existing C++ code at Google is not
> prepared to deal with exceptions, it
> is comparatively difficult to adopt
> new code that generates exceptions.
>
> Given that Google's existing code is
> not exception-tolerant, the costs of
> using exceptions are somewhat greater
> than the costs in in a new project.
> The conversion process would be slow
> and error-prone. We don't believe that
> the available alternatives to
> exceptions, such as error codes and
> assertions,
introduce a significant
> burden.
>
> Our advice against using exceptions is
> not predicated on philosophical or
> moral grounds, but practical ones.
> Because we'd like to use our
> open-source projects at Google and
> it's difficult to do so if those
> projects use exceptions, we need to
> advise against exceptions in Google
> open-source projects as well. Things
> would probably be different if we had
> to do it all over again from scratch.

[GO: Defer, Panic and Recover][5]

> Defer statements allow us to think about closing each file right after opening it, guaranteeing that, regardless of the number of return statements in the function, the files will be closed.
>
> The behavior of defer statements is straightforward and predictable. There are three simple rules:
>
> 1. A deferred function's arguments are evaluated when the defer statement is evaluated.
>
> In this example, the expression "i" is evaluated when the Println call is deferred. The deferred call will print "0" after the function returns.
>
>
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
>
>
> 2. Deferred function calls are executed in Last In First Out order after the surrounding function returns. This function prints "3210":
>
>
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
>
>
> 3. Deferred functions may read and assign to the returning function's named return values.
>
> In this example, a deferred function increments the return value i after the surrounding function returns. Thus, this function returns 2:
>
func c() (i int) {
defer func() { i++ }()
return 1
}
>
> This is convenient for modifying the error return value of a function; we will see an example of this shortly.
>
> Panic is a built-in function that stops the ordinary flow of control and begins panicking. When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller. To the caller, F then behaves like a call to panic. The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes. Panics can be initiated by invoking panic directly. They can also be caused by runtime errors, such as out-of-bounds array accesses.
>
> Recover is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.
>
> Here's an example program that demonstrates the mechanics of panic and defer:
>
> <snip>
>
>For a real-world example of panic and recover, see the json package from the Go standard library. It decodes JSON-encoded data with a set of recursive functions. When malformed JSON is encountered, the parser calls panic is to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value (see the 'error' and 'unmarshal' functions in decode.go). There is a similar example of this technique in the Compile routine of the regexp package. The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.
>
>Other uses of defer (beyond the file.Close() example given earlier) include releasing a mutex:
>
> mu.Lock()
> defer mu.Unlock

1: http://golang.org/doc/effective_go.html#multiple-returns "Effective GO: Multiple return values"
[2]: http://golang.org/doc/effective_go.html#named-results "GO: Named result parameters"
[3]: http://golang.org/doc/go_faq.html#exceptions "Why does Go not have exceptions?"
[4]: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Exceptions#Exceptions "Google C++ Style Guide: Exceptions"
[5]: http://blog.golang.org/2010/08/defer-panic-and-recover.html "GO: Defer, Panic and Recover"

答案1

得分: 14

多个返回值在Go语言中并不是独特的,它们也不能替代异常。从C(或C++)的角度来看,它们是一种简洁且用户友好的替代方案,用于返回包含多个值的结构体(对象)。

如果你只是想表示错误,它们确实提供了一种方便的方式。

为什么“断言”被认为是替代方案?

断言最初用于调试。它们在程序处于“不可能”状态时停止执行,这种状态在设计上不应该发生,但实际上却发生了。返回错误可能帮助不大。显然,代码库还没有正常工作,那么它如何能够成功恢复呢?当存在需要注意的错误时,为什么还要让它恢复呢?

在生产代码中使用断言是一个有所不同的问题 - 显然存在性能和代码大小的考虑,因此通常的做法是在代码分析和测试已经确信“不可能”情况真的是不可能之后将其删除。但是,如果你在这个级别的偏执下运行代码,即它正在自我审计,那么你可能也担心,如果让它继续在“不可能”状态下运行,它可能会做一些危险的破坏性操作:破坏有价值的数据,超出堆栈分配并可能创建安全漏洞。因此,你只想尽快关闭它。

你使用断言的目的与使用异常的目的并不相同:当像C++和Java这样的编程语言为“不可能”情况(logic_errorArrayOutOfBoundsException)提供异常时,它们无意中鼓励一些程序员认为他们的程序应该尝试从实际上已经失控的情况中恢复。有时这是合适的,但Java建议不要捕获RuntimeException是有原因的。很少情况下捕获一个RuntimeException是一个好主意,这也是它们存在的原因。几乎总是不建议捕获它们,这意味着它们会停止程序(或至少停止线程)。

英文:

Multiple returns are not unique to Go, and they're not a substitute for exceptions. In C (or C++) terms, they are a concise and user-friendly substitute for returning a struct (object) containing multiple values.

They do provide a convenient means of indicating errors, if that's all you mean.

<i>Why are "asserts" considered alternatives?</i>

Asserts are initially for debugging. They halt the program in situations where it is in an "impossible" state, one that the design says should not happen, but which has anyway. Returning an error is unlikely to help much. The code base obviously doesn't work yet, so how on earth can it successfully recover? Why would you even want it to, when there's a bug that needs attention?

Using asserts in production code is a bit of a different matter - obviously there are performance and code size concerns, so the usual approach is to remove them once your code analysis and tests have convinced you that the "impossible" situations really are impossible. But, if you're running code at this level of paranoia, that it's auditing itself, then you're probably also paranoid that if you let it carry on running in an "impossible" state, then it might do something dangerously broken: corrupting valuable data, overrunning a stack allocation and perhaps creating security vulnerabilities. So again, you just want to shut down as soon as possible.

The stuff you use asserts for really isn't the same as the stuff you use exceptions for: when programming languages like C++ and Java provide exceptions for "impossible" situations (logic_error, ArrayOutOfBoundsException), they unintentionally encourage some programmers to think that their programs should attempt to recover from situations where really they're out of control. Sometimes that is appropriate, but the Java advice not to catch RuntimeExceptions is there for a good reason. Very occasionally it's a good idea to catch one, which is why they exist. Almost always it's not a good idea to catch them, meaning that they amount to halting the program (or at least the thread) anyway.

答案2

得分: 4

你应该阅读一些关于异常的文章,以认识到返回值并不是异常。不是以C中的“in-band”方式,也不是以其他任何方式。

不深入讨论,异常的目的是在发现错误条件时抛出,并在可以有意义地处理错误条件的地方捕获。返回值只在堆栈层次结构中的第一个函数中处理,该函数可能或可能不知道如何处理问题。一个简单的例子是一个可以将值作为字符串检索的配置文件,并支持处理成类型化的返回语句:

class config {
   // 抛出key_not_found异常
   string get( string const &amp; key );
   template &lt;typename T&gt; T get_as( string const &amp; key ) {
      return boost::lexical_cast&lt;T&gt;( get(key) );
   }
};

现在的问题是如果找不到键应该如何处理。如果使用返回码(比如go语言中的方式),问题在于get_as必须处理get的错误码并相应地采取行动。由于它不知道该怎么做,唯一明智的做法是手动向上传播错误:

class config2 {
   pair&lt;string,bool&gt; get( string const &amp; key );
   template &lt;typename T&gt; pair&lt;T,bool&gt; get_as( string const &amp; key ) {
      pair&lt;string,bool&gt; res = get(key);
      if ( !res.second ) {
          try {
             T tmp = boost::lexical_cast&lt;T&gt;(res.first);
          } catch ( boost::bad_lexical_cast const &amp; ) {
             return make_pair( T(), false ); // 不可转换
          }
          return make_pair( boost::lexical_cast&lt;T&gt;(res.first), true );
      } else {
          return make_pair( T(), false ); // 错误条件
      }
   }
}

类的实现者必须添加额外的代码来转发错误,而且这些代码与实际问题的逻辑混在一起。在C++中,这可能比设计用于多重赋值的语言(a,b=4,5)更加繁琐,但是如果逻辑依赖于可能的错误(在这里只有在有实际字符串时才调用lexical_cast),那么你仍然需要将值缓存到变量中。

英文:

You should read a couple of articles on exceptions to realize that return values are not exceptions. Not in the C 'in-band' way or in any other way.

Without going into a deep argument, exceptions are meant to be thrown where the error condition is found and captured where the error condition can be meaningfully handled. Return values are only processed in the very first function up the hierarchy stack, that could or could not how to process the problem. A simple example would be a configuration file that can retrieve values as strings, and also supports processing into typed return statements:

class config {
   // throws key_not_found
   string get( string const &amp; key );
   template &lt;typename T&gt; T get_as( string const &amp; key ) {
      return boost::lexical_cast&lt;T&gt;( get(key) );
   }
};

Now the problem is how do you handle if the key was not found. If you use return codes (say in the go-way) the problem is that get_as must handle the error code from get and act accordingly. As it does not really know what to do, the only sensible thing is manually propagating the error upstream:

class config2 {
   pair&lt;string,bool&gt; get( string const &amp; key );
   template &lt;typename T&gt; pair&lt;T,bool&gt; get_as( string const &amp; key ) {
      pair&lt;string,bool&gt; res = get(key);
      if ( !res.second ) {
          try {
             T tmp = boost::lexical_cast&lt;T&gt;(res.first);
          } catch ( boost::bad_lexical_cast const &amp; ) {
             return make_pair( T(), false ); // not convertible
          }
          return make_pair( boost::lexical_cast&lt;T&gt;(res.first), true );
      } else {
          return make_pair( T(), false ); // error condition
      }
   }
}

The implementor of the class must add extra code to forward the errors, and that code gets intermixed with the actual logic of the problem. In C++ this is probably more burdensome than in a language designed for multiple assignments (a,b=4,5) but still, if the logic depends on the possible error (in here calling lexical_cast should only be performed if we have an actual string) then you will have to cache values into variables anyway.

答案3

得分: 3

这不是Go语言,而是在Lua中,多返回值是处理异常的一种非常常见的习惯用法。

如果你有一个函数像这样:

function divide(top,bottom)
   if bottom == 0 then 
        error("不能除以零")
   else
        return top/bottom
   end
end

那么当bottom为0时,会引发一个异常并且程序的执行会停止,除非你将函数divide包装在pcall(或受保护的调用)中。

pcall总是返回两个值:第一个结果是一个布尔值,告诉你函数是否成功返回,第二个结果要么是返回值,要么是错误消息。

下面这个(人为制造的)Lua片段展示了它的使用:

local top, bottom = get_numbers_from_user()
local status, retval = pcall(divide, top, bottom)
if not status then
    show_message(retval)
else
    show_message(top .. " 除以 " .. bottom .. " 是 " .. retval)
end

当然,你不必使用pcall,如果你调用的函数已经以status, value_or_error的形式返回。

多返回值对于Lua来说已经足够好用了好几年,所以虽然这并不能确保它对于Go来说也足够好用,但它支持这个想法。

英文:

It's not Go, but in Lua, multiple return is an extremely common idiom for handling exceptions.

If you had a function like

function divide(top,bottom)
   if bottom == 0 then 
        error(&quot;cannot divide by zero&quot;)
   else
        return top/bottom
   end
end

Then when bottom was 0, an exception would be raised and the program's execution would halt, unless you wrapped the function divide in a pcall (or protected call).

pcall always returns two values: the first is result is a boolean telling whether the function returned successfully, and the second result is either the return value or the error message.

The following (contrived) Lua snippet shows this in use:

local top, bottom = get_numbers_from_user()
local status, retval = pcall(divide, top, bottom)
if not status then
    show_message(retval)
else
    show_message(top .. &quot; divided by &quot; .. bottom .. &quot; is &quot; .. retval)
end

Of course, you don't have to use pcall, if the function you're calling already returns in the form of status, value_or_error.

Multiple return has been good enough for Lua for several years, so while that doesn't ensure that it's good enough for Go, it is supportive of the idea.

答案4

得分: 2

是的,错误返回值很好,但不能捕捉到异常处理的真正含义...即在通常情况下不打算处理的异常情况的能力和管理。

Java(例如)的设计认为异常是有效的工作流程场景,他们对接口和库必须声明和版本化这些抛出的异常的复杂性有一定的观点,但是异常在堆栈中扮演着重要的角色。

想象一下,如果在几十个方法调用深处有条件地处理异常返回码的情况。堆栈跟踪会显示出哪一行代码有问题?

英文:

Yes, error return values are nice but do not capture the true meaning of Exception handling...that is the ability and management of exceptional cases in which one does not normally intend.

The Java (i.e.) design considers Exceptions IMO to be valid workflow scenarios and they have a point about the complexity of interfaces and libraries having to declare and version these thrown exception, but alas Exceptions serve an important role in the stack domino.

Think of the alternative case where exceptional return codes are conditionally handled in several dozen method calls deep. What would stack traces look like in terms of where the offending line number is?

答案5

得分: 2

这个问题有点棘手,很难客观地回答,对于异常的观点可能会有很大的差异。

但是如果我要猜测的话,我认为Go语言没有包含异常的主要原因是它会使编译器变得复杂,并且在编写库时可能会导致非常复杂的影响。异常很难处理好,他们优先考虑的是让一些东西能够正常工作。

通过返回值处理错误和使用异常处理错误的主要区别在于,异常会强制程序员处理异常情况。除非你显式地捕获异常并在catch块中什么都不做,否则你永远不会有一个“静默错误”。另一方面,在函数内部隐式返回点会导致其他类型的错误。这在C++中尤为常见,因为你需要显式地管理内存,并确保你永远不会丢失指向已分配的内存的指针。

以下是C++中一个危险情况的示例:

struct Foo {
    // 如果B的构造函数抛出异常,你会泄漏A对象。
    Foo() : a(new A()), b(new B()) {}
    ~Foo() { delete a; delete b; }

    A *a;
    B *b;
};

多返回值使得实现基于返回值的错误处理更加容易,而无需依赖函数的输出参数,但从根本上并没有改变任何东西。

一些语言同时具有多返回值和异常(或类似的机制)。一个例子是Lua

英文:

This question is a bit tricky to answer objectively, and opinions on exceptions can differ quite a lot.

But if I were to speculate, I think the primary reason exceptions is not included in Go is because it complicates the compiler and can lead to non-trivial implications when writing libraries. Exceptions is hard to get right, and they prioritized getting something working.

The primary difference between handling errors through return values and exceptions is that exceptions forces the programmer to deal with unusual conditions. You can never have a "silent error" unless you explicitly catch an exception and do nothing in the catch block. On the other hand, you get implicit return points everywhere inside functions which can lead to other types of bugs. This is especially prevalent with C++ where you manage memory explicitly and need to make sure you never lose a pointer to something you have allocated.

Example of dangerous situation in C++:

struct Foo {
    // If B&#39;s constructor throws, you leak the A object.
    Foo() : a(new A()), b(new B()) {}
    ~Foo() { delete a; delete b; }

    A *a;
    B *b;
};

Multiple return values makes it easier to implement return value based error handling without having to rely on out arguments to functions, but it doesn't change anything fundamentally.

Some languages have both multiple return values and exceptions (or similar mechanisms). One example is Lua.

答案6

得分: 1

这里是一个示例,展示了在C++中如何使用多个返回值。我不会自己编写这段代码,但我认为使用这种方法并不完全不可行。

#include
#include
#include
using namespace std;

// 返回值类型
template
struct RV {
int mStatus;
T mValue;

RV(int status, const T & rv) 
    : mStatus(status), mValue(rv) {}
int Status() const { return mStatus; }
const T & Value() const {return mValue; }

};

// 可能的使用示例
RV ReadFirstLine(const string & fname) {
ifstream ifs(fname.c_str());
string line;
if (!ifs) {
return RV(-1, "");
}
else if (getline(ifs, line)) {
return RV(0, line);
}
else {
return RV(-2, "");
}
}

// 使用示例
int main() {
RV r = ReadFirstLine("stuff.txt");
if (r.Status() == 0) {
cout << "Read: " << r.Value() << endl;
}
else {
cout << "Error: " << r.Status() << endl;
}
}

英文:

Here's an example of how multiple return values might work in c++. I wouldn't write this code myself, but I don't think it is entirely out of the question to use such an approach.

#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;string&gt;
using namespace std;

// return value type
template &lt;typename T&gt; 
struct RV {
	int mStatus;
	T mValue;

	RV( int status, const T &amp; rv ) 
		: mStatus( status ), mValue( rv ) {}
	int Status() const { return mStatus; }
	const T &amp; Value() const {return mValue; }
};

// example of possible use
RV &lt;string&gt; ReadFirstLine( const string &amp; fname ) {
	ifstream ifs( fname.c_str() );
	string line;
	if ( ! ifs ) {
		return RV &lt;string&gt;( -1, &quot;&quot; );
	}
	else if ( getline( ifs, line ) ) {
		return RV &lt;string&gt;( 0, line );
	}
	else {
		return RV &lt;string&gt;( -2, &quot;&quot; );
	}
}

// in use
int main() {
	RV &lt;string&gt; r = ReadFirstLine( &quot;stuff.txt&quot; );
	if ( r.Status() == 0 ) {
		cout &lt;&lt; &quot;Read: &quot; &lt;&lt; r.Value() &lt;&lt; endl;
	}
	else {
		cout &lt;&lt; &quot;Error: &quot; &lt;&lt; r.Status() &lt;&lt; endl;
	}
}

答案7

得分: -2

如果您需要使用C++的方式来实现一个“可为空”的对象,请使用boost::optional< T >。您可以将其作为布尔值进行测试,如果评估为true,则对其进行解引用以获得有效的T。

英文:

If you need the C++ way of doing a "nullable" object use boost::optional< T >. You test it as a boolean and if it evaluates true then you dereference it to a valid T.

huangapple
  • 本文由 发表于 2009年12月5日 21:19:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/1852186.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定