为什么抛出异常不被视为在运行时返回不同数据类型的技术?

wmj*_*gla 2 c++ exception return-type c++11

我心里有这样的想法:

// This is called at multiple locations and each handles the returned object
// differently. Handling also differs depending on the returned object type.
void GetObject() {
    // Check runtime condition

    // Object1 and Object2 are completely dissimilar and do not share a base class.
    if (condition1) {
        // Operations to prepare for construction of Object1
        throw Object1{ var1, var2, var3 };
    }
    if (condition2) {
        // Operations to prepare for construction of Object2
        throw Object2{ var4, var5 };
    }
    throw MyException{};
}

// Usage example
int main() {
    try {
        GetObject();
    }
    catch (const Object1& obj1) {
        // Object1-specific handling
    }
    catch (const Object2& obj2) {
        // Object2-specific handling
    }
    catch (const MyException& e) {
        // Error handling
    }
}
Run Code Online (Sandbox Code Playgroud)

奇怪的是,有关返回不同数据类型的问题的答案根本没有提到这种技术:

这有什么特别的原因吗?我能想到的唯一原因是它是非正统的,否则这似乎是最简单和最干净的方法 - 没有第 3 方库,不需要升级到 C++17,没有额外的抽象。

更新

正如许多人在评论中指出的那样,std::variant可以很好地解决问题,但它需要 C++17 或 boost 库;不幸的是,我的项目目前无法使用其中任何一个。

更新2

感谢大家的投入。他们非常有帮助,让我思考各种设计选项并突出显示我忽略的领域。我选择了下面的答案,因为它直接解决了不考虑使用该技术的原因。其他 2 个答案提出了替代技术,因此如果您有类似的用例,请查看它们。对我来说,我最终决定使用输出参数作为解决方案,因为它很简单,而且我没有 C++17 也没有 boost 的限制:

bool GetObject(
    std::unique_ptr<Object1>& obj1,
    std::unique_ptr<Object2>& obj2) {
    // Check runtime condition

    if (condition1) {
        // Operations to prepare for construction of Object1
        obj1 = std::make_unique<Object1>(var1, var2, var3);
        return true;
    }
    if (condition2) {
        // Operations to prepare for construction of Object2
        obj2 = std::make_unique<Object2>(var4, var5);
        return true;
    }
    return false;
}

// Usage example
int main() {
    std::unique_ptr<Object1> obj1;
    std::unique_ptr<Object2> obj2;
    if (!GetObject(obj1, obj2)) {
        // Error handling
        return -1;
    }
    if (obj1) {
        // Object1-specific handling
    }
    else {
        // Object2-specific handling
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

如果对象不太大,静态分配对象(即在堆栈上)也可以代替 unique_ptr 。但这需要Object1和Object2具有可以告诉调用者该类是否已初始化的成员函数。

Jan*_*tke 5

对于多态性来说,异常的成本太高

异常只是错误的多态机制。抛出异常是一个极其昂贵的操作: 比较不同基准的相对成本的图表。 与 use_error_code 相关的所有结果都相当低(50 或更低),但抛出异常的概率越大,use_exception 条就会显着升高(use_exception90 超过 4000)。
资料来源:@Arash 的答案包含此基准

在此基准测试中,无论概率有多大,返回错误代码的成本基本相同。 抛出异常可能会慢 100 倍以上。 它很贵,因为

  • 堆栈必须被展开。这实际上是单线程的,并且需要同步,请参阅P2544:C++ 异常变得越来越有问题
  • 运行的代码可能不会被缓存,因为异常处理代码永远不会正常执行。
  • 异常在动态内存中分配。

如果异常如此昂贵,我们什么时候使用它们?

只要您从不抛出异常,那么它们的开销就为零,但如果您抛出异常,那么您就会陷入疯狂的境地。这就是为什么,正如其名称所示,它们用于特殊情况,例如:

  • 分配时内存不足
  • 从驱动程序或操作系统收到意外错误
  • 开发人员犯的错误,例如访问错误的索引std::vector::at

在常规程序执行中,永远不应该抛出异常。

返回 N 类型之一的替代方法

这个主题已经在您链接的线程中讨论得很彻底,但只是为了给您一些选择:

  • std::variant(C++17)
  • std::optionalstd::expected特定情况下的(C++17) 和(C++23)
  • 多态类
    • 可能包含在std::unique_ptr(C++11)中
  • struct它只包含两种类型,以及一个表示哪个类型处于活动状态的标签
    • 可能比适当的效率低union,但实施起来很简单
  • 通过静态多态性完全避免运行时多态性
    • 可能通过模板

你的例子有点太小了,无法说明哪种方式在这里效果最好。