C ++のオーバーロードされたコンストラクターによる不明な型の変数の初期化


22

主にpythonのバックグラウンドから来ているので、C ++で型を操作するのに多少苦労しました。

異なる型をパラメーターとして受け取るいくつかのオーバーロードされたコンストラクターの1つを介してクラス変数を初期化しようとしています。autoキーワードの使用は変数の自動宣言に使用できることを読みましたが、私の場合、コンストラクターが選択されるまで初期化されません。しかし、コンパイラーは初期化しないことに満足していませんvalue

class Token {
public:

    auto value;

    Token(int ivalue) {
        value = ivalue;
    }
    Token(float fvalue) {
        value = fvalue;
    }
    Token(std::string svalue) {
        value = svalue;
    }

    void printValue() {
        std::cout << "The token value is: " << value << std::endl;
    }
};

Pythonでは、次のようになります。

class Token():
        def __init__(self, value):
             self.value = value

        def printValue(self):
             print("The token value is: %s" % self.value)

autoこのシナリオでキーワードを使用する正しい方法は何ですか?完全に別のアプローチを使用する必要がありますか?


2
autoクラスのメンバーにはまったく使用できないと思いますか?関連するが古くなった質問:「自動」メンバー変数を持つことは可能ですか?
Yksisarvinen

テンプレートを使用しない理由はありますか?
ジミーRT

Pythonでは、型は実行時に各操作で決定されます。オーバーヘッドが必要ですが、変数の型をステートメント間で変更できます。C ++では、コードをコンパイルできるように、事前に型を知っておく必要があります。floatとintには異なるバイナリレイアウトがあり、使用するには異なるアセンブリ命令が必要です。実行時に柔軟性が必要な場合は、バリアントなどの共用体タイプを使用して、タイプごとに有効なコードを含む多くのブランチの1つを選択し、パフォーマンスのオーバーヘッドを追加する必要があります。intとfloatのバージョンを分けておきたい場合は、テンプレートが役立ちます。
ジェイク

回答:


17

C ++のオーバーロードされたコンストラクターによる不明な型の変数の初期化

C ++には「不明な型の変数」などはありません。

このシナリオでautoキーワードを使用する正しい方法は何ですか?

自動推定された変数には、初期化子から推定された型があります。イニシャライザがない場合は、autoを使用できません。autoは、非静的メンバー変数には使用できません。クラスの1つのインスタンスは、別のインスタンスとは異なる型のメンバーを持つことはできません。

このシナリオでは、autoキーワードを使用する方法はありません。

完全に別のアプローチを使用する必要がありますか?

多分。を実装しようとしているようですstd::variant。X個のタイプの1つを格納する変数が必要な場合は、それを使用する必要があります。

ただし、C ++で動的型付けをエミュレートしようとしている可能性があります。Pythonの経験があるため、使い慣れているかもしれませんが、多くの場合、それは理想的なアプローチではありません。たとえば、この特定のサンプルプログラムでは、メンバー変数を使用して行うことは、それを出力することだけです。したがって、いずれの場合も文字列を格納する方が簡単です。他のアプローチは、Rhathinによって示される静的なポリモーフィズムまたはFire Lancerによって示されるOOPスタイルの動的なポリモーフィズムです。


この場合、unionの使用も対象になりますか?
wondra

unionエラーが発生しやすい低レベルのメカニズムです。variantおそらく内部で使用し、その使用をより安全にします。
Erlkoenig

@wondraユニオン自体は、現在アクティブなメンバーを検査できないため、それだけではあまり役に立ちません。std :: stringのような自明ではないクラス(カスタムデストラクタを持つ)で使用するのも非常に困難です。タグ付けされたユニオンが必要です。これはstd :: variantが実装するデータ構造です。
eerorika

1
libstdc ++ variant はを使用ますunion。代わりに、生のメモリと新しい配置を使用して、constexprコンストラクターで使用することはできません。
Erlkoenig

@Erlkoenigは十分に公正です、私は私が言ったことを取り戻します。私は、ユニオンを使用しないブーストの実装のみを見て、誰もが同じことをすると想定していました。
eerorika

11

C ++は静的に型付けされた言語です。つまり、すべての変数の型は実行前に決定されます。したがって、autoキーワードはvar、動的に型付けされる言語であるJavaScriptのキーワードのようなものではありません。autoキーワードは通常、不必要に複雑なタイプを指定するために使用されます。

探しているものは、代わりにC ++テンプレートクラスを使用して行われる可能性があります。これにより、異なる型を取るクラスの複数のバージョンを作成できます。

このコードはあなたが探している答えかもしれません。

template <typename T>
class Token {
private:
    T value;

public:
    Token(const T& ivalue) {
        value = ivalue;
    }

    void printValue() {
        std::cout << "The token value is: " << value << std::endl;
    }
};

このコードは、関数operator<<がstd :: ostream&およびタイプTに対して定義されている必要があるなど、いくつかの条件が満たされた場合にコンパイルされます。


6

他の提案とは異なるアプローチは、テンプレートを使用することです。次に例を示します。

template<class T>
class Token {
public:

    T value;

    Token(T value) :
        value(std::move(value))
    {}

    void printValue() {
        std::cout << "The token value is: " << value << std::endl;
    }
};

その後、次のようにクラスを使用できます。

Token<int> x(5);
x.printValue();

3

std::variantタイプを使用できます。以下のコードは1つの方法を示しています(ただし、少し不格好です。私は認めざるを得ません)。

#include <iostream>
#include <variant>

class Token {
public:

    std::variant<int, float, std::string> value;

    Token(int ivalue) {
        value = ivalue;
    }
    Token(float fvalue) {
        value = fvalue;
    }
    Token(std::string svalue) {
        value = svalue;
    }

    void printValue() {
        switch (value.index()) {
            case 0:
                std::cout << "The token value is: " << std::get<0>(value) << std::endl;
                break;
            case 1:
                std::cout << "The token value is: " << std::get<1>(value) << std::endl;
                break;
            case 2:
                std::cout << "The token value is: " << std::get<2>(value) << std::endl;
                break;
        }
    }
};

int main() {
    Token it(1);
    Token ft(2.2f);
    Token st("three");
    it.printValue();
    ft.printValue();
    st.printValue();
    return 0;
}

std::get<0>(value)ように書くことができればもっといいでしょうstd::get<value.index()>(value)が、悲しいことに、「x」は<x>コンパイル時の定数式でなければなりません。


1
おそらくのstd::visit代わりに使用する方が良いでしょうswitch
eerorika

1

auto 特定の型に推定可能でなければなりません。ランタイムの動的型付けを提供しません。

宣言Tokenするときに、使用できるすべての可能な型std::variant<Type1, Type2, Type3>などがわかっている場合、これは「型列挙型」と「共用体」を持つことに似ています。適切なコンストラクタとデストラクタが呼び出されるようにします。

std::variant<int, std::string> v;
v = "example";
v.index(); // 1, a int would be 0
std::holds_alternative<std::string>(v); // true
std::holds_alternative<int>(v); // false
std::get<std::string>(v); // "example"
std::get<int>(v); // throws std::bad_variant_access

別の方法としてはToken、適切な仮想メソッドを使用して、(場合によってはテンプレートを使用して)ケースごとに異なるサブタイプを作成することもできます。

class Token {
public:
    virtual void printValue()=0;
};

class IntToken : public Token {
public:
    int value;
    IntToken(int ivalue) {
        value = ivalue;
    }
    virtual void printValue()override
    {
        std::cout << "The token value is: " << value << std::endl;
    }
}

0

以下の解決策は、ファイアランサーの回答の精神と似ています。主な違いは、おそらくテンプレートを使用してコメントに従うため、インターフェイスの派生インスタンスを明示的に作成する必要がなくなることです。Tokenそれ自体はインターフェースクラスではありません。代わりに、インターフェイスを内部クラスとして定義し、派生テンプレートの定義を自動化する内部テンプレートクラスを定義します。

その定義は過度に複雑に見えます。ただし、Token::Baseインターフェースを定義し、インターフェースToken::Impl<>から派生します。これらの内部クラスは、のユーザーに対して完全に隠されていますToken。使用法は次のようになります。

Token s = std::string("hello");
Token i = 7;

std::cout << "The token value is: " << s << '\n';
std::cout << "The token value is: " << i << '\n';

また、以下のソリューションは、変換演算子を実装してTokenインスタンスを通常の変数に割り当てる方法を示しています。これはに依存しておりdynamic_cast、キャストが無効な場合は例外をスローします。

int j = i; // Allowed
int k = s; // Throws std::bad_cast

の定義Tokenは以下のとおりです。

class Token {

    struct Base {
        virtual ~Base () = default;
        virtual std::ostream & output (std::ostream &os) = 0;
    };

    template <typename T>
    struct Impl : Base {
        T val_;
        Impl (T v) : val_(v) {}
        operator T () { return val_; }
        std::ostream & output (std::ostream &os) { return os << val_; }
    };

    mutable std::unique_ptr<Base> impl_;

public:

    template <typename T>
    Token (T v) : impl_(std::make_unique<Impl<T>>(v)) {}

    template <typename T>
    operator T () const { return dynamic_cast<Impl<T>&>(*impl_); }

    friend auto & operator << (std::ostream &os, const Token &t) {
        return t.impl_->output(os);
    }
};

オンラインでお試しください!

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