Confiar na conversão implícita de argumentos é considerado perigoso?

10

O C ++ possui um recurso (não consigo descobrir o nome apropriado), que chama automaticamente os construtores correspondentes dos tipos de parâmetros se os tipos de argumento não forem os esperados.

Um exemplo muito básico disso é chamar uma função que espera a std::stringcom um const char*argumento. O compilador irá gerar código automaticamente para chamar o std::stringconstrutor apropriado .

Gostaria de saber, é tão ruim para a legibilidade como eu acho que é?

Aqui está um exemplo:

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);

Tudo bem? Ou vai longe demais? Se não devo fazê-lo, posso de alguma forma fazer Clang ou GCC avisar sobre isso?

futlib
fonte
11
e se o Draw fosse sobrecarregado com uma versão de string posteriormente?
catraca aberração
11
de acordo com a resposta de @Dave Rager, não acho que isso seja compilado em todos os compiladores. Veja meu comentário sobre a resposta dele. Aparentemente, de acordo com o padrão c ++, você não pode encadear conversões implícitas como essa. Você pode fazer apenas uma conversão e não mais.
Jonathan Henson
OK, desculpe, na verdade não compilei isso. Atualizado o exemplo e ainda é horrível, IMO.
Futlib 16/03/2013

Respostas:

24

Isso é chamado de construtor de conversão (ou construtor implícito ou conversão implícita).

Não conheço uma opção em tempo de compilação para avisar quando isso ocorre, mas é muito fácil evitar isso; basta usar a explicitpalavra - chave

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

Se a conversão ou não de construtores é uma boa ideia: depende.

Circunstâncias em que a conversão implícita faz sentido:

  • A classe é barata o suficiente para construir e você não se importa se for implicitamente construída.
  • Algumas classes são conceitualmente semelhantes aos seus argumentos (como std::stringrefletir o mesmo conceito do qual const char *ele pode implicitamente converter); portanto, a conversão implícita faz sentido.
  • Algumas classes se tornam muito mais desagradáveis ​​de usar se a conversão implícita estiver desabilitada. (Pense em ter que chamar explicitamente std :: string toda vez que você quiser passar um literal de string. Partes do Boost são semelhantes.)

Circunstâncias em que a conversão implícita faz menos sentido:

  • A construção é cara (como o exemplo da Textura, que exige carregar e analisar um arquivo gráfico).
  • As classes são conceitualmente muito diferentes dos seus argumentos. Considere, por exemplo, um contêiner do tipo matriz que tome seu tamanho como argumento:
    classe FlagList
    {
        FlagList (int tamanho_inicial); 
    };

    void SetFlags (const FlagList & flag_list);

    int main () {
        // Agora isso compila, mesmo que não seja óbvio
        // o que está fazendo.
        SetFlags (42);
    }
  • A construção pode ter efeitos colaterais indesejados. Por exemplo, uma AnsiStringclasse não deve implicitamente construir a partir de a UnicodeString, pois a conversão Unicode em ANSI pode perder informações.

Leitura adicional:

Josh Kelley
fonte
3

Isso é mais um comentário do que uma resposta, mas grande demais para ser colocado em um comentário.

Curiosamente, g++não me deixa fazer isso:

#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;
}

Produz o seguinte:

$ 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&)

No entanto, se eu alterar a linha para:

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

Ele realizará essa conversão.

Dave Rager
fonte
Essa é uma "característica" interessante do g ++. Suponho que seja um bug que verifique apenas um tipo em profundidade, em vez de diminuir recursivamente o máximo possível no tempo de compilação para gerar o código correto, ou há um sinalizador que precisa ser definido no seu comando g ++.
Jonathan Henson
11
pt.cppreference.com/w/cpp/language/implicit_cast , parece que o g ++ está seguindo o padrão estritamente. É o compilador da Microsoft ou Mac que está sendo um pouco generoso com o código do OP. Especialmente reveladora é a afirmação: "Ao considerar o argumento para um construtor ou para uma função de conversão definida pelo usuário, apenas uma sequência de conversão padrão é permitida (caso contrário, as conversões definidas pelo usuário podem ser efetivamente encadeadas)."
Jonathan Henson
Sim, eu apenas juntei o código para testar algumas das gccopções do compilador (que parece não haver nenhuma para resolver esse caso em particular). Eu não olhei muito a fundo (eu deveria estar trabalhando :-), mas dada gcca aderência ao padrão e o uso da explicitpalavra-chave uma opção de compilador provavelmente foi considerada desnecessária.
Dave Rager
Conversões implícitas não são encadeadas e Textureprovavelmente não deve ser construído implicitamente (de acordo com as diretrizes em outras respostas); portanto, seria um site de chamada melhor renderer.Draw(Texture("foo.png"));(supondo que funcione conforme o esperado).
Bluesorblade #
3

É chamado de conversão implícita de tipo. Em geral, é uma coisa boa, pois inibe repetições desnecessárias. Por exemplo, você obtém automaticamente uma std::stringversão Drawsem precisar escrever nenhum código extra. Também pode ajudar a seguir o princípio de aberto-fechado, pois permite expandir Rendereros recursos do mesmo sem se modificar Renderer.

Por outro lado, não é sem inconvenientes. Pode dificultar descobrir de onde vem um argumento, por um lado. Às vezes, pode produzir resultados inesperados em outros casos. É para isso que explicitserve a palavra-chave. Se você o colocar no Textureconstrutor, desabilitará o uso desse construtor para conversão implícita de tipo. Não conheço um método para alertar globalmente sobre a conversão implícita de tipo, mas isso não significa que não exista um método, apenas que o gcc tem um número incompreensivelmente grande de opções.

Karl Bielefeldt
fonte