web-dev-qa-db-ja.com

暗黙の引数変換に依存することは危険だと考えられていますか?

C++には、引数の型が期待されるものでない場合に、パラメーターの型の一致するコンストラクターを自動的に呼び出す機能があります(適切な名前がわかりません)。

これの非常に基本的な例は、std::string引数を持つconst 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にそれについて警告させることができますか?

10
futlib

これは、変換コンストラクター(または、暗黙のコンストラクターまたは暗黙の変換)と呼ばれます。

これが発生したときに警告するコンパイル時の切り替えについては知りませんが、簡単に防ぐことができます。 explicitキーワードを使用するだけです。

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

コンストラクタを変換するかどうかは良い考えです。

暗黙的な変換が意味をなす状況:

  • このクラスは、暗黙的に構築されているかどうかに関係なく構築できるほど安価です。
  • 一部のクラスは概念的にそれらの引数に似ています(std::stringなど、暗黙的に変換できるconst char *と同じ概念を反映している)ため、暗黙的な変換は理にかなっています。
  • 暗黙的な変換が無効になっている場合、一部のクラスは使用がはるかに不愉快になります。 (文字列リテラルを渡すたびに明示的にstd :: stringを呼び出す必要があると考えてください。Boostの部分は似ています。)

暗黙的な変換があまり意味をなさない状況:

  • 構築にはコストがかかります(テクスチャの例など、グラフィックファイルの読み込みと解析が必要)。
  • クラスは概念的には引数と非常に似ていません。たとえば、サイズを引数として取る配列のようなコンテナを考えてみます。
クラスFlagList 
 {
 FlagList(int initial_size); 
}; 
 
 void SetFlags(const FlagList&flag_list); 
 
 int main(){
 //これでコンパイルされます、まったく明らかではありませんが、
 //何をしているか。
 SetFlags(42); 
} 
  • 建設には望ましくない副作用があるかもしれません。たとえば、AnsiStringクラスは ない UnicodeからANSIへの変換では情報が失われる可能性があるため、UnicodeStringから暗黙的に構成します。

参考文献:

24
Josh Kelley

これは回答というよりコメントですが、コメントを入れるには大きすぎます。

興味深いことに、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"));

その変換を実行します。

3
Dave Rager

これは暗黙の型変換と呼ばれます。一般的に、それは不必要な繰り返しを抑制するので、良いことです。たとえば、std::stringバージョンのDrawのために追加のコードを記述する必要はありません。また、Renderer自体を変更せずにRendererの機能を拡張できるため、オープン/クローズの原則に従うのにも役立ちます。

一方、欠点がないわけではありません。一つには、議論がどこから来ているのかを理解するのが難しい場合があります。他の場合には、予期しない結果が生じることがあります。それがexplicitキーワードの目的です。 Textureコンストラクターに配置すると、暗黙的な型変換にそのコンストラクターを使用できなくなります。暗黙的な型変換についてグローバルに警告するメソッドは知りませんが、それはメソッドが存在しないという意味ではなく、gccに理解できないほど多くのオプションがあるということだけです。

3
Karl Bielefeldt