【问题标题】:C++ conversion operator vs constructor precedence, compilers differC ++转换运算符与构造函数优先级,编译器不同
【发布时间】:2014-07-28 04:41:25
【问题描述】:

我有 2 个非常简单的类,ClassA 和 ClassB。我希望能够从 ClassB 转换为 ClassA。使用 Sun C++ 5.11,它可以很好地编译并完全按照我的预期运行,根据:Conversion constructor vs. conversion operator: precedence

当我尝试使用 gcc 版本 4.8.2 编译它时,它给出了一个关于对重载函数的模棱两可调用的错误。当强制转换似乎是一种定义明确的行为时,为什么会有不同的行为?

守则

main.cc

#include <iostream>

class ClassB;

class ClassA
{
    public:
    ClassA( const int& num )
    : _number( num )
    { std::cout << "ClassA int constructor\n"; }

    private:
    int _number;
};

class ClassB
{
    public:
    ClassB( const int& num )
    : _number( num )
    { std::cout << "ClassB int constructor\n"; }

    operator ClassA() const throw()
    {
        std::cout << "ClassB operator ClassA()\n";
        return ClassA( _number );
    }
    operator int() const throw()
    {
        std::cout << "ClassB operator int()\n";
        return _number;
    }

    private:
    int _number;
};

int main( int argc, const char* argv[] )
{
    std::cout << "Creating b:\n";
    ClassB b( 5 );
    std::cout << "Casting b to a ClassA:\n";
    ClassA a = static_cast<ClassA>( b );
}

使用 Sun C++ 5.11,它可以正常编译并输出以下输出:

Creating b:
ClassB int constructor
Casting b to a ClassA:
ClassB operator ClassA()
ClassA int constructor 

使用gcc,编译器吐出如下错误:

main.cc: In function 'int main(int, const char**)':
main.cc:43:36: error: call of overloaded 'ClassA(ClassB&)' is ambiguous
  ClassA a = static_cast<ClassA>( b );
                                    ^
main.cc:43:36: note: candidates are:
main.cc:8:2: note: ClassA::ClassA(const int&)
  ClassA( const int& num )
  ^
main.cc:5:7: note: ClassA::ClassA(const ClassA&)
 class ClassA

如果我注释掉 ClassB::operator int() 或 ClassA::ClassA( const int& ),它可以正常编译并给出相同的输出。如果我注释掉 ClassB::operator CLassA(),我会得到以下输出:

Creating b:
ClassB int constructor
Casting b to a ClassA:
ClassB operator int()
ClassA int constructor

为什么gcc认为这两个转换序列是等价的:

ClassB::运算符 ClassA()
ClassB::operator int() -> ClassA::ClassA( const int& )

【问题讨论】:

  • 要演示这个问题的代码太多了。
  • 一些额外的上下文:在实际出现的代码中,有一个“ClassA”,但有很多不同的“ClassB”需要转换为“ClassA”。提出的一个解决方案是实现一个方法 ClassB::toClassA(),它返回一个与 ClassB::operator ClassA() 相同的 ClassA 对象。虽然这将使这个编译并正常工作,但我想了解导致当前方法不起作用的实际情况。
  • @chris:怎么会这样?为了演示它,我需要两个类,一个具有多个转换运算符,一个具有多个构造函数。我还尝试使实现尽可能与实际类相似,以防我忽略的一些实现细节是问题。
  • 您应该将它放在一个文件中,我们可以将其复制粘贴到编辑器中(因为问题不在于多个文件)。与其担心它与你的代码很接近,你应该尽可能地减少它,然后验证它是否仍然存在同样的问题。
  • 包含调试输出的代码实际上很有帮助。

标签: c++ gcc casting


【解决方案1】:

这应该可行:

int main( int argc, const char* argv[] )
{
    std::cout << "Creating b:\n";
    ClassB b( 5 );
    std::cout << "Casting b to a ClassA:\n";
    ClassA a = b.operator ClassA();
}

这里发生的事情是static_cast 在您的各种转换运算符之间感到困惑。它试图从 ClassA 到 ClassB,但由于 C++ 允许在函数调用中进行一次隐式转换,并且有多种可能的转换路径(ClassB 到浮动到 ClassA,ClassB 到 int 到 ClassA,ClassB 直接到 ClassA),编译器抱怨关于歧义。 (好吧,在您编辑之前,浮动路径就在那里......)

调用所需的转换运算符直接解决歧义,因为不再有任何潜在的隐式转换。

编辑:下面 40two 的回答(在转换运算符上使用 explicit)也很好地消除了模棱两可的路径,同时仍然允许直接转换。这可能是你想走的路。

【讨论】:

  • 我个人认为这本身并不是一个坏习惯。这是一个更晦涩难懂的习语,因此我会努力解释我在 cmets 中所做的事情,以造福于未来的维护者,但它本身并没有错——它几乎完全按照标签上所说的那样做。跨度>
  • 我可以使用更明显的成语吗?现在,我正在使用的代码中充斥着从 ClassB 到 ClassA 的各种类型的转换。我曾希望我可以通过以某种方式更改 ClassB 来解决它,因为那将是最小的更改,但是如果我自己经历并更改演员表,我想使用最正确的方法。
  • 如果您想将更改范围限制为ClassB 以使现有的转换工作,唯一要做的就是消除转换中的歧义。除了 ClassB 到 ClassA 之外,您是否可以消除所有其他用户定义的转换?
  • 实际上,40two 的想法可能会让您以最小的变化,使用您拥有的 gcc 版本。
【解决方案2】:

如果您的编译器支持 C++11 功能,您可以显式声明转换运算符:

class ClassB
{
    public:
    explicit ClassB( const int& num );

    explicit operator ClassA() const throw();
    explicit operator int() const throw();
    explicit operator float() const throw();

    int getNumber() const;

    private:
    int _number;
}

Display

因此,编译器不会在重载解析中与模棱两可的调用混淆 static_cast&lt;&gt; 由于隐式转换,必须从中进行选择。

【讨论】:

    猜你喜欢
    • 2010-11-25
    • 2012-09-02
    • 1970-01-01
    • 2012-05-04
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 2017-07-04
    相关资源
    最近更新 更多