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


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 ++標準によると、このような暗黙の変換を連鎖させることはできません。変換できるのは1回だけで、それ以上はできません。
ジョナサンヘンソン

申し訳ありませんが、実際にはこれをコンパイルしていません。例を更新しましたが、それは恐ろしいことです、IMO。
futlib 2013年

回答:


24

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

これが発生したときに警告するコンパイル時の切り替えについては知りませんが、簡単に防ぐことができます。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);
    }
  • 建設には望ましくない副作用があるかもしれません。たとえば、UnicodeからANSIへの変換では情報が失われる可能性があるため、AnsiStringクラスはから暗黙的に構築しないでUnicodeStringください。

参考文献:


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 ++の興味深い「機能」です。それは、正しいコードを生成するためにコンパイル時に可能な限り再帰的に下に行くのではなく、1つの型の深さのみをチェックするバグか、g ++コマンドで設定する必要があるフラグがあるかのどちらかだと思います。
ジョナサンヘンソン

1
en.cppreference.com/w/cpp/language/implicit_cast g ++は標準に厳密に従っているようです。OPのコードに少し寛大すぎるのは、MicrosoftまたはMacのコンパイラーです。「コンストラクタまたはユーザー定義の変換関数への引数を検討する場合、許可される標準の変換シーケンスは1つだけです(そうでない場合、ユーザー定義の変換は効果的に連鎖できます)。」
Jonathan Henson

ええ、私はいくつかのgccコンパイラオプションをテストするためにコードを一緒に投げました(この特定のケースに対処するものはないようです)。私はそれについて詳しく調べませんでした(私は動作しているはずです:-)がgcc標準に準拠していること、およびexplicitキーワードの使用、コンパイラオプションはおそらく不要であると考えられました。
Dave Rager 2013年

暗黙的な変換は連鎖されず、Textureおそらく(他の回答のガイドラインに従って)暗黙的に構築されるべきではないので、より適切な呼び出しサイトrenderer.Draw(Texture("foo.png"));(期待どおりに動作すると想定)になります。
Blaisorblade、2015年

3

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.