【问题标题】:C++: Can I make an assignment operator "explicit"C ++:我可以使赋值运算符“显式”吗
【发布时间】:2016-07-19 07:06:52
【问题描述】:

我的任务是在 C++ 类库中迁移错误处理的概念。以前简单返回 bool(成功/失败)的方法应修改为返回 Result 对象,该对象传达机器可读的错误代码和人类可读的解释(还有一些在这里无关紧要)。

遍历数千行代码很容易出错,因此我尝试从编译器获得最好的支持来完成这项任务。

我的结果类具有 - 除其他成员方法外 - 一个构造函数,用于从代码构造结果和代码的赋值运算符:

class Result
{
    public:
        typedef unsigned long ResultCode;
        explicit Result(ResultCode code); // (1)
        Result& operator=(ResultCode code); // (2)
};

备注:我通常会为ResultCode 使用枚举类,这可以解决我的问题,但这不是一个选项。这是因为主要的设计目标是在不同的库中使用Result,每个库都应定义自己的一组结果代码,而不需要一个为所有库定义所有可能结果代码的大头文件。实际上,每个类都应该能够定义本地结果代码,以便可以从类头中获得可能的结果代码列表。因此代码不能在Result 中枚举,它们必须由使用Result 类的类定义。

为了避免隐式转换

return true;

客户端代码中的语句,构造函数已被显式声明。但是在嵌套方法调用中,又出现了一个问题。说,我有一个方法

bool doSomething()
{
    return true;
}

我在返回Result 对象的函数中使用它。我想转发嵌套调用的结果代码

Result doSomethingElse
{
    Result result = doSomething();
    return result; 
}

使用Result 的赋值运算符的当前实现,这不会给我一个编译器错误 - doSomething() 的布尔返回值被隐式转换为 unsigned long。

正如我在 C++ 文档中所读到的,只有构造函数和转换运算符可以被显式声明。

我的问题

  1. 为什么赋值运算符或其他方法不允许显式? IMO 允许任何方法都是显式的也是很有意义的。
  2. 是否有其他解决方案可以防止赋值运算符的隐式类型转换?

【问题讨论】:

  • 我知道这不是你想要的答案,但为什么不在 C++ 中使用异常机制呢?不要与语言抗争。使用它。 (不过,对于提出的问题,还要加一个)。
  • 您不能声明template<typename T> void operator=(T) = delete; 并保留您拥有的那个吗?它将在正常情况下使用该方法,并尝试对所有其他类型使用已删除的方法。
  • 如果我在 coliru 上尝试这个,我会得到 error: conversion from 'bool' to non-scalar type 'Result' requested,没有进一步调查。
  • @Bathsheba:我喜欢使用异常和其他 C++ 东西。但是,这些库应作为共享库提供,并且不建议跨 SL 边界抛出异常。异常在内部使用,但对于与应用程序的接口,它们应转换为结果
  • 您上面的代码没有使用赋值。以上任何内容均不排除使用enum class。你的问题是“我不想用 bbq 来做这个蛋糕。recipie 说要用 350 F 的烤箱,我不能用 bbq,因为我对蜜蜂过敏。为什么我的烤箱不工作?” .您了解这如何使回答变得棘手。我的意思是,烤箱和烧烤之间有相似之处,也许对蜜蜂过敏并不是避免使用烤箱的真正原因,也许你的烤箱真的不工作,但这个问题似乎掩盖了混淆而不是描述问题。

标签: c++


【解决方案1】:

您的问题不在Result 类中:毕竟,您正在明确地创建它的新实例; explicit 不能禁止。

我认为你不能禁止隐式提升bool -> long

您可以解决它。一种方法是使 ResultCode not 成为整数类型。那么,it 可以有一个显式的构造函数。类似的东西

class ResultCode 
{
unsigned long m_code;
public:
explicit ResultCode(unsigned long code) : m_code(code) {}
operator unsigned long () { return m_code; } 
};

将允许您在任何可以使用unsigned int 的地方使用ResultCode,并将其创建为ResultCode res = 5return ResultCode(5),但不能调用期望ResultCode 的函数(例如Result 构造函数!)已经不是ResultCode 的任何东西,如果函数必须返回ReturnCode,也不要做类似return 5 的东西。

否则,您可以使用模板重载来“捕获”不是unsigned int 的任何内容并强制其为错误

typedef unsigned long ResultCode;

class Result
{
    ResultCode m_par;

public: 
    template<typename T>
    Result(T param) { static_assert(false); }

    template<>
    Result(ResultCode par): m_par(par) {}
};

int main()
{
    ResultCode a = 5;     //ok
    //unsigned long a = 6;  //also ok
    //bool a = true;      //error!
    //int a = 7;          //also error!!
    Result b(a);
}

【讨论】:

    【解决方案2】:

    使用 Result 的赋值运算符的当前实现,这不会给我一个编译器错误 - doSomething() 的布尔返回值被隐式转换为 unsigned long。

    关于您发布的代码;它确实会导致错误error: no viable conversion from 'bool' to 'Result'see here

    需要一个显示您在代码中看到的行为的最小示例。实际代码中可能有其他构造函数或转换类型会对您的代码产生重大影响。


    关于明确提出的问题...

    为什么赋值运算符或其他方法不允许显式?

    explicit 仅允许在可以进行隐式转换的情况下使用,即编译器会尝试为您生成转换的情况(bool 有一个特殊情况)。此类转换是构造函数和转换(或强制转换运算符)。

    将构造函数或转换运算符标记为explicit 可防止编译器进行转换,因此,如果您需要转换,则需要明确说明 - 作为这样做的一般动机,它使代码更明确地说明它的作用。有一个权衡,所以在这两种情况下都应该明智地使用。一般建议是在有疑问时支持explicit

    例如;

    struct Result {
      Result(long src); // can be marked explicit
      operator long() const; // can be marked explicit
    };
    

    是否有其他解决方案可以防止赋值运算符的隐式类型转换?

    赋值运算符有一个特定的Result&amp; operator=(Result&amp;);。在分配本身中,没有转换。为了防止为赋值隐式创建Result,构造函数需要标记为explicit

    为防止ResultResultCode 创建,您可以不声明该方法,或者将其标记为已删除;

    Result& operator=(ResultCode code) = delete;
    

    【讨论】:

    • 如果不是转换,那么在幕后发生了什么 Result result = true;那么结果代码为 1
    • 你用的是什么编译器?这给了我在 gcc、clang 和 msvc 中的错误。
    • @kritzel_sw。您是否有一个显示编译的最小示例(这与您的预期不同)?
    • 将尝试剥离它。需要几天,因为我要出差了
    猜你喜欢
    • 2019-04-16
    • 2020-09-11
    • 2021-10-22
    • 2019-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-13
    • 1970-01-01
    相关资源
    最近更新 更多