依靠隐式参数转换是否被认为是危险的?


10

C ++具有一项功能(我无法弄清楚它的正确名称),如果参数类型不是期望的参数类型,它将自动调用参数类型的匹配构造函数。

一个非常基本的示例就是调用一个std::stringconst char*参数的函数。编译器将自动生成代码以调用适当的std::string构造函数。

我想知道,它是否像我认为的那样对可读性不利?

这是一个例子:

class Texture {
public:
    Texture(const std::string& imageFile);
};

class Renderer {
public:
    void Draw(const Texture& texture);
};

Renderer renderer;
std::string path = "foo.png";
renderer.Draw(path);

这样好吗 还是太过分了?如果我不应该这样做,我可以以某种方式让Clang或GCC发出警告吗?


1
如果Draw在以后用字符串版本重载了怎么办?
棘轮怪胎

1
根据@Dave Rager的回答,我认为这不会在所有编译器上进行编译。看我对他的回答的评论。显然,根据c ++标准,您不能像这样链接隐式转换。您只能进行一次转换,而不能进行更多转换。
乔纳森·亨森

好的,抱歉,实际上没有编译它。更新了该示例,它仍然很可怕,IMO。
futlib

Answers:


24

这称为转换构造函数(或有时称为隐式构造函数或隐式转换)。

我不知道会在发生这种情况时发出警告的编译时开关,但是很容易防止。只需使用explicit关键字。

class Texture {
public:
    explicit Texture(const std::string& imageFile);
};

至于转换构造函数是否是一个好主意:这取决于。

隐式转换有意义的情况:

  • 该类足够便宜,以至于您不需要关心它是否是隐式构造的。
  • 有些类在概念上与它们的参数相似(例如,std::string反映了与其const char *隐式转换的概念相同的概念),因此隐式转换是有意义的。
  • 如果禁用了隐式转换,则某些类将变得更加令人讨厌。(考虑到每次要传递字符串文字时,都必须显式调用std :: string。Boost的各个部分相似。)

隐式转换的意义较弱的情况:

  • 构造非常昂贵(例如您的Texture示例,该示例需要加载和解析图形文件)。
  • 从概念上讲,类与它们的参数非常不同。例如,考虑一个以其大小作为参数的类似数组的容器:
    类FlagList
    {
        FlagList(int initial_size); 
    };

    无效的SetFlags(const FlagList&flag_list);

    int main(){
        //现在可以编译,即使它并不明显
        //它在做什么。
        SetFlags(42);
    }
  • 施工可能会产生有害的副作用。例如,AnsiString类应该隐式地从一个构建体UnicodeString中,由于Unicode到ANSI转换可能会丢失信息。

进一步阅读:


3

这更多的是评论,而不是答案,但是太大了,无法发表评论。

有趣的是,g++不允许我这样做:

#include <iostream>
#include <string>

class Texture {
        public:
                Texture(const std::string& imageFile)
                {
                        std::cout << "Texture()" << std::endl;
                }
};

class Renderer {
        public:
                void Draw(const Texture& texture)
                {
                        std::cout << "Renderer.Draw()" << std::endl;
                }
};

int main(int argc, char* argv[])
{
        Renderer renderer;
        renderer.Draw("foo.png");

        return 0;
}

产生以下内容:

$ g++ -o Conversion.exe Conversion.cpp 
Conversion.cpp: In function int main(int, char**)’:
Conversion.cpp:23:25: error: no matching function for call to Renderer::Draw(const char [8])’
Conversion.cpp:14:8: note: candidate is: void Renderer::Draw(const Texture&)

但是,如果我将行更改为:

   renderer.Draw(std::string("foo.png"));

它将执行该转换。


确实,这是g ++中一个有趣的“功能”。我想这可能是一个只检查一种类型的错误,而不是在编译时尽可能地递归向下以生成正确代码的错误,或者是在您的g ++命令中需要设置一个标志。
乔纳森·亨森

1
en.cppreference.com/w/cpp/language/implicit_cast似乎g ++严格遵循了该标准。是Microsoft或Mac的编译器对OP的代码过于宽容。特别要说明的是以下语句:“在考虑构造函数或用户定义的转换函数的参数时,仅允许一个标准转换序列(否则可以有效地链接用户定义的转换)。”
Jonathan Henson,2013年

是的,我只是将代码放在一起以测试一些gcc编译器选项(看起来没有任何一种可以解决这种特殊情况)。我没有对它进行深入研究(我应该是在工作:-),但是考虑gcc到遵循标准并使用explicit关键字编译器选项,可能会认为它是不必要的。
戴夫·拉格

隐式转换不是链式的,并且a Texture可能不应隐式构造(根据其他答案的准则),因此,更好的调用站点将是renderer.Draw(Texture("foo.png"));(假设它按我的预期工作)。
Blaisorblade

3

这称为隐式类型转换。总的来说,这是一件好事,因为它可以防止不必要的重复。例如,您自动获得的std::string版本,Draw而无需为此编写任何其他代码。它还可以帮助您遵循开闭原则,因为它使您Renderer无需修改即可扩展其功能Renderer

另一方面,它也不是没有缺点。一方面,它可能使弄清楚一个论点从何而来。在其他情况下,有时可能会产生意外的结果。这就是explicit关键字的目的。如果将其放在Texture构造函数上,它将禁用使用该构造函数进行隐式类型转换。我不知道一种全局警告隐式类型转换的方法,但这并不意味着不存在一种方法,只是gcc拥有大量的选项。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.