【问题标题】:Unconditionally throw *this from constructor从构造函数中无条件抛出 *this
【发布时间】:2017-05-30 13:38:32
【问题描述】:

我相信从构造函数体内抛出 *this 是合法的。我想知道我对此是否完全错误(在对象的构造函数完成之前传递对象的问题?)或者这在风格上是否令人厌恶。

我发布了第二个更具体的问题here,该问题已得到全面解答。这篇文章中的代码是合法的,如果有点奇怪的话。

给定一个异常结构:

struct fail final : public std::logic_error
{
  fail() : std::logic_error("fail") {}
  using std::logic_error::logic_error;
};

还有一堆呼叫网站,例如:

throw fail();
// or
throw fail("oh dear");

我正在考虑将结构更改为:

struct fail final : public std::logic_error
{
  fail() : std::logic_error("fail") { throw * this; }
  fail(const char* w) : std::logic_error(w) { throw * this; }
};

此时调用站点可以保持不变,或者被改写为更短的:

fail();
// or
fail("oh dear");

这实质上意味着我不再需要到处写 throw。我还可以使用名称“fail”继续捕获异常。这似乎确实有效,但让我怀疑我以后可能会后悔这个选择。

谢谢

编辑:稍微考虑一下行为。

1/ Throw *this 将生成 *this 的副本,或者如果这对复制省略算公平游戏,则移动它,因此 logic_error 触发的析构函数不是问题

2/ 没有成员的类的默认复制构造函数可能只是基类的复制构造函数,所以可能可以复制 *this

3/ 通过异常返回的 *this 的副本对于任何未在初始化列表中设置的成员可能具有未定义的值

4/ 可以在构造过程中调用成员函数。 (默认)复制构造函数是一个成员函数,因此可以在构造过程中调用。 throw *this 将调用复制构造函数。所以我仍然相信代码是合法的

【问题讨论】:

  • throw有什么不好?我不敢相信你经历所有这些只是为了让代码更难阅读。
  • 如果您真的想避免一遍又一遍地重复throw,请考虑将您的错误处理登录包装到包含整个抛出的高阶函数 /try/catch 错误处理流程。要么全部抽象掉,要么一个都不抽象。
  • throw *this 的合法性如何?在构造函数中抛出异常会取消构造并自动销毁任何已构造的成员,因此您最终会得到一个死对象。还是throw先复制?
  • @JonChesterfield :如果派生类构造函数抛出该类被认为未构造并且调用基类的析构函数来清理混乱。捕手正在捕捉一个被破坏的物体。
  • @JonChesterfield : 构造函数正常返回后,如果构造函数抛出,对象的析构函数将不会被调用,因为它从未被认为是构造的。

标签: c++ exception


【解决方案1】:

我对您编写富有表现力的代码的愿望表示同情。

这是我在我们的代码库中使用的那种东西,特别是在调用将成功或失败返回为类似布尔整数的 C API 时。

#include <stdexcept>
#include <exception>

struct failure : std::runtime_error
{
  using runtime_error::runtime_error;
};

template<class Message>
[[noreturn]]
bool fail(Message&& msg)
{
  throw failure(std::forward<Message>(msg));
}

int main()
{
  extern bool didSomething();

  didSomething() or fail("couldn't do it");
}

例外情况更有趣:

#include <stdexcept>
#include <exception>
#include <cstdio>
#include <memory>
#include <system_error>
#include <sstream>
#include <iostream>
#include <iomanip>


namespace native
{
    struct no_message {};

    constexpr no_message join() { return {}; }

    template<class First, class...Rest>
    std::string join(First&& first, Rest&&...rest)
    {
        std::ostringstream ss;
        ss << first;
        using expand = int[];
        void(expand{ 0,
                     ((ss << ' ' << rest),0)...
        });
        return ss.str();
    }

    [[noreturn]]
    void throwSystemError(no_message, int code)
    {
        throw std::system_error(code, std::system_category());
    }

    template<class Message>
    [[noreturn]]
    void throwSystemError(Message&& message, int code)
    {
        throw std::system_error(code, std::system_category(), message);
    }

    template<class...Parts>
    [[noreturn]]
    bool systemError(Parts&&...parts)
    {
        auto err = errno;
        throwSystemError(join(std::forward<Parts>(parts)...), err);
    }

    struct file_closer {
        void operator()(FILE* fp) const noexcept {
            std::fclose(fp);
        }
    };
    using FilePtr = std::unique_ptr<FILE, file_closer>;

    bool valid(FilePtr const& p) { return p.get(); }

    FilePtr openFile(const char* path, const char* mode)
    {
        auto ptr = FilePtr(std::fopen(path, mode));
        valid(ptr) or systemError("opening file", std::quoted(path), "in mode", std::quoted(mode));
        return ptr;
    }
}


int main()
try
{
    auto fptr = native::openFile("ibetthisdoesntexist.txt", "rb");
}
catch(std::system_error const& syserr)
{
    std::cerr << "system error: "
              << syserr.what()
              << ", error code " << syserr.code().value()
              << std::endl;
    std::exit(100);
}

示例输出:

system error: opening file "ibetthisdoesntexist.txt" in mode "rb": No such file or directory, error code 2

【讨论】:

  • 您真的使用 ISO 646 替代令牌吗?这似乎将“表达性代码”发挥到了极致,牺牲了编写惯用的 C++。事实上,我不确定隐藏 throw 语句是否有助于提高可读性。
  • 我一直使用 ISO 646 进行布尔逻辑。它不仅更具可读性,而且当您打算编写 &amp;&amp; 时,也无法编写 &amp;。出于几个原因,raise 调用很有用。首先,它推迟了 throw,因此只需很少的重构,我就可以在 throw 站点插入(例如)日志记录或指标。第二个是我对像 perl 这样的脚本语言有一种不健康的喜爱:)
  • 有趣。我喜欢动词用fail,名词用fail。这也很好地消除了代码的歧义。谢谢
【解决方案2】:

我看到的两个缺点:

  • 不是惯用的,所以隐藏throw会让用户感到惊讶。所以 IMO,它的可读性较差。

  • 您不能从您的类继承(因为该基类抛出)(您可以标记类 final(来自 C++11)以突出显示它)。

【讨论】:

  • 我大致同意非惯用语。毕竟,它看起来很奇怪,足以激发这个问题。继承限制是一个很好的观察,我已将结构标记为 final(在实验分支和问题中),谢谢
猜你喜欢
  • 2011-11-04
  • 2013-01-01
  • 2023-03-11
  • 2011-04-07
  • 2010-10-29
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多