【问题标题】:Explicit keyword applied to operator instead of constructor显式关键字应用于运算符而不是构造函数
【发布时间】:2018-09-24 15:47:36
【问题描述】:

在下面的类中,

你为什么要让运营商explicit。我以为explicit是为了防止隐式调用构造函数?

 class Content
            {
public:

 virtual ~Content() = 0;
 virtual explicit operator float&();
 virtual explicit operator long long&();
 virtual explicit operator std::string&()
}

【问题讨论】:

  • explicit 防止隐式转换。用户定义的转换运算符也可以允许隐式转换,除非 explicit。
  • "我认为显式是为了防止隐式调用构造函数?" 你为什么认为它只适用于构造函数?谁告诉你的?
  • @AlgirdasPreidžius:任何使用 C++03 或更早版本编译器的人 ;-)

标签: c++ c++11 operator-keyword explicit


【解决方案1】:

我认为显式是为了防止隐式调用 构造函数?

从 C++11 开始,它也适用于 user-defined conversions(又名 cast 运算符)。

你为什么要让运营商explicit

在这种情况下,explicit 关键字使转换仅适用于直接初始化和显式转换。请参阅[class.conv.fct¶2] 下的此处:

转换函数可能是显式的([dcl.fct.spec]),在这种情况下 它仅被视为用户定义的转换 直接初始化 ([dcl.init])。否则,用户定义 转换不限于在作业中使用,并且 初始化。

这可以帮助您确保编译器不会按照您的意图尝试转换,因此您必须自己显式转换它,从而减少出错的空间。示例:

struct Foo
{
    explicit operator int() {return 0;}
    operator int*() {return nullptr;}
};

int main()
{
    Foo foo;

    //int xi = foo; // Error, conversion must be explicit
    int i = static_cast<int>(foo); // OK, conversion is explicit
    int* i_ptr = foo; // OK, implicit conversion to `int*` is allowed

    int i_direct(foo); // OK, direct initialization allowed
    int* i_ptr_direct(foo); // OK, direct initialization after implicit conversion

    return 0;
}

它还可以在应用多个转换选项的情况下帮助解决歧义,从而使编译器没有标准来决定选择哪一个:

struct Bar
{
    operator int() {return 1;}
    operator char() {return '1';}
};

int main()
{
    Bar bar;    
    //double d = bar; // Error, implicit conversion is ambiguous    
    return 0;
}

添加explicit:

struct Bar
{
    operator int() {return 1;}
    explicit operator char() {return '1';}
};

int main()
{
    Bar bar;    
    double d = bar; // OK, implicit conversion to `int` is the only option    
    return 0;
}

【讨论】:

  • 恕我直言,作为一般规则,用户定义类型的转换(如果这些是绝对必要的)应该是成员函数,例如:int Foo::toInt()、bool Foo::toBool();那么......如果方便客户端代码中的清晰性/安全性,定义显式转换运算符(例如,explicit Foo::operator int()),并且只有在有非常强大的原因时,才使用隐式转换运算符。 (清晰、明确、简单)比聪明更重要。
  • @ÁlvaroGustavoLópez 同意:)但说到明确(哈)我试图明确回答所提出的问题——即关于使用 explicit 关键字进行转换。
  • 我的是评论,不是答案。
  • @ÁlvaroGustavoLópez 你是 100% 好的,我只是对为什么不首先将它包含在答案中而道歉:)
【解决方案2】:

考虑以下几点:

struct Content
{
    operator float() { return 42.f; }  
    friend Content operator+(Content& lhs, float) { return lhs; }
};

int main()
{
    Content c{};
    c + 0; //  error: ambiguous overload for 'operator+'
}

在这里,编译器无法在operator+(Content&amp;, float) 和operator+(float, int) 之间进行选择。使浮点运算符explicit 解决了这种歧义*:

c + 0; // operator+(Content&, float)

或

static_cast<float>(c) + 0; // operator+(float, int)

*) 前提是偏好其中一个是有意义的。

【讨论】:

    【解决方案3】:

    其他答案涵盖了它是如何工作的,但我认为应该告诉你为什么将它添加到 C++ 中。

    智能指针通常会转换为bool,因此您可以这样做:

    std::shared_ptr<int> foo;
    if (foo) {
      *foo = 7;
    }
    

    其中if(foo) 将foo 转换为bool。不幸的是:

    int x = foo+2;
    

    将foo 转换为bool,然后转换为int,然后添加2。这几乎总是一个错误。这是允许的,因为虽然只完成了一次用户定义的转换,但用户定义的转换后跟内置的转换可以静默发生。

    为了解决这个问题,程序员会做一些疯狂的事情,比如添加:

    struct secret {
      void unused();
    };
    struct smart_ptr {
      using secret_mem_ptr = void(secret::*)();
      operator secret_mem_ptr() const { return 0; }
    };
    

    而secret_mem_ptr 是一个指向成员的秘密指针。指向成员的指针具有到 bool 的内置转换,因此:

    smart_ptr ptr;
    if (!ptr) {
    }
    

    "works" -- ptr 转换为secret_mem_ptr,然后转换为bool,然后用于决定采用哪个分支。

    这不仅仅是一个黑客攻击。

    他们在转换运算符上添加了explicit 来解决这个确切的问题。

    现在:

    struct smart_ptr {
      explicit operator bool() const { return true; }
    };
    

    不允许:

    smart_ptr ptr;
    int x = 3 + ptr;
    

    但它确实允许:

    if (ptr) {
    }
    

    因为规则是手工制定的,以完全支持该用例。它也不允许:

    bool test() {
      smart_ptr ptr;
      return ptr;
    }
    

    在这里,您必须输入:

    bool test() {
      smart_ptr ptr;
      return (bool)ptr;
    }
    

    您将ptr 显式转换为bool。

    (我通常非常反对 C 风格的强制转换;我在 (bool) 的情况下例外)。

    【讨论】:

    • "我通常真的反对 C 风格的强制转换;我在 (bool) 的情况下例外" - 为什么?调用显式转换时应始终使用static_cast:return static_cast&lt;bool&gt;(ptr); 如果定义了这样的转换,则会调用它,否则代码将无法编译
    • @RemyLebeau 找我一个例子,(bool)(x) 做错(或危险)的事情,而static_cast&lt;bool&gt;(x) 做正确的事情(包括由于错误而导致编译失败,当演员表是危险的) .如果可以,我会重新考虑,但我不能。
    【解决方案4】:

    如果您希望 Content 对象永远不会被隐式转换为(例如)float,则可以使用它。这可能通过以下方式发生:

     void f( float f );
     ....
     Content c;
     f( c );      // conversion from Content to float
    

    没有explicit 限定符,转换会隐式发生;有了它,你会得到一个编译错误。

    无声的、隐式的转​​换可能是很多混乱和/或错误的根源,因此通常最好使运算符为显式,或者最好提供命名函数,例如ToFloat,它告诉读者到底发生了什么。

    【讨论】:

      猜你喜欢
      • 2017-12-21
      • 2021-07-27
      • 1970-01-01
      • 2016-07-18
      • 1970-01-01
      • 2014-02-17
      • 2010-11-10
      • 1970-01-01
      • 2015-08-21
      相关资源
      最近更新 更多