【问题标题】:assignment operator for class derived from std::exception从 std::exception 派生的类的赋值运算符
【发布时间】:2020-02-16 03:46:50
【问题描述】:

我从std::runtime_error派生了一个自定义异常类

静态分析器给我一个警告,如果我定义或删除默认操作(复制 ctors、复制/移动操作符、析构函数等),我应该定义或删除它们。

为了解决这个愚蠢的警告,我写了缺少的赋值运算符,但后来我收到另一个警告,现在我的运算符隐藏了基本的非虚拟赋值运算符!

由于基类有我无法复制的私有成员,看来唯一的解决方案是直接为基对象部分调用基赋值运算符,然后复制 *this 对象的其余部分并最终返回 *this

但在此之前,我查看了基础 operator= 的功能以及它的外观:

exception& operator=(exception const& _Other) noexcept
{
    if (this == &_Other)
    {
        return *this;
    }

    __std_exception_destroy(&_Data);
    __std_exception_copy(&_Other._Data, &_Data);
    return *this;
}
private:

    __std_exception_data _Data;
};

现在知道这是我的实现(使用 cmets)来调用基本分配并复制派生对象的其余部分:

class Exception :
    public std::runtime_error
{
public:

    // ...

    Exception& operator=(const Exception& other)
    {
        if (this == &other)
        {
            return *this;
        }

        // first copy only base class data to *this
        *dynamic_cast<std::runtime_error*>(this) =
            runtime_error::operator=(
                *dynamic_cast<std::runtime_error*>(
                    const_cast<Exception*>(&other)));

        // then copy derived class data to *this
        mInfo = other.mInfo;
        mCode = other.mCode;

        // finally return complete copy
        return *this;
    }

private:
    std::error_code mCode;
    std::string mInfo;
};

这是正确的方法吗?我认为这看起来很麻烦,但我不确定。

编辑

这是完整的课程,供参考:

#pragma warning (disable : 4275)    // base needs to have DLL interface
    class ERROR_API Exception :
        public std::runtime_error
    {
    public:
        ~Exception() noexcept;  // cant be inlined in release build

        // default/delete
        Exception(const Exception&) = default;
        Exception(Exception&&) = delete;

        Exception& operator=(const Exception& other)
        {
            if (this == &other)
            {
                return *this;
            }

            // copy base class data to *this
            *dynamic_cast<std::runtime_error*>(this) =
                runtime_error::operator=(
                    *dynamic_cast<std::runtime_error*>(
                        const_cast<Exception*>(&other)));

            // copy derived class data to *this
            mInfo = other.mInfo;
            mCode = other.mCode;

            return *this;
        }

        Exception& operator=(Exception&&) = delete;

        /** Construct from error enum */
        template<typename Enum>
        Exception(Enum err_enum);

        /** Construct from error enum and string*/
        template<typename Enum>
        Exception(Enum err_enum, String message);

        /** Construct from error_code object */
        inline Exception(std::error_code err_code);

        /** Construct from error_code object and string */
        inline Exception(std::error_code err_code, String message);

        /** Get error_condidtion name */
        inline virtual std::string ConditionName() const;

        /** Get error_category name */
        inline virtual std::string CategoryName() const;

        /** Get error_condition value */
        inline virtual int ConditionValue() const noexcept;

        /** Get error_condition value */
        inline virtual int ErrorValue() const noexcept;

        /** Get additional information string passed to constructor */
        inline virtual const String& GetInfo() const noexcept;

        /** Get error_code object associated with this exception object */
        inline virtual const std::error_code& code() const noexcept;

    private:
        SUPPRESS(4251);     // member needs to have DLL interface
        std::error_code mCode;
        SUPPRESS(4251);     // member needs to have DLL interface
        String mInfo;
    };
#pragma warning (default : 4275)    // base needs to have DLL interface

【问题讨论】:

  • 那么为什么需要实现或删除任何特殊成员呢?这看起来像是一个很好的零规则。
  • 请在开始添加内容之前发布原始课程,只是基础知识,也许它可以按原样进行,并且您的静态分析器(哪个?)过于激进。
  • 首先,静态分析器为您提供了两种可能的方法来解决此问题。我想你选错了,删除这些是我的选择。也就是说,所有 dynamic_cast 的东西都不是必需的,只要记住 operator= 也可以作为方法调用,所以你只需调用 runtime_error::operator=(other) 来分派到基类。
  • 如果您打算实现其他默认操作,则让编译器定义它们,直到您准备好定义自己的操作。最多将它们定义为您可以在以后完成的存根。无论如何,隐藏警告可能是因为您的operator=() 与继承的不一致。 std::exceptions operator=() 具有 throw() 规范(C++11 之前)或 noexcept(C++11 及更高版本)。您的派生类需要与之保持一致 - 否则您的 operator=() 会根据标准隐藏基类版本。
  • 鉴于您的静态分析器不同于编译器及其库(这是定义std::exception 和派生类的地方),您可能对此无能为力。只需记录静态分析器发出警告的事实,并且您已经确定它是可以接受的(即误报)。如果您的静态分析器支持此类事情,请在 Exception 类中添加注释,以防止静态分析器发出警告。您需要阅读静态分析器的文档以确定这是否可行,如果可行,如何实现。

标签: c++ inheritance assignment-operator


【解决方案1】:

感谢来自 Ulrich Eckhardt、Peter 和其他人的优秀 cmets,这就是我的工作方式,结果完全没有警告:

class Exception : public std::runtime_error
{
public:
    ~Exception() noexcept;  // can't be inlined in release build (defaulted in source)

    // default/delete
    Exception(const Exception&) noexcept = default;
    Exception(Exception&&) noexcept = default;
    Exception& operator=(const Exception&) noexcept = default;
    Exception& operator=(Exception&&) noexcept(false) = deault;

    // the rest of the code ...
};

【讨论】:

  • 在我无法删除移动分配但该类不(也不能)支持移动语义的类中,我只是通过Exception&amp; operator=(Exception&amp;&amp; that) noexcept { *this = that; return *this; } 来压制静态分析器。我还发表了一条评论,解释了为什么做了坏事,对于必须维护该代码的可怜的灵魂。
猜你喜欢
  • 1970-01-01
  • 2012-02-12
  • 2021-07-31
  • 1970-01-01
  • 2016-07-10
  • 2019-05-23
  • 2011-11-13
相关资源
最近更新 更多