web-dev-qa-db-ja.com

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

主に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キーワードを使用する正しい方法は何ですか?まったく別のアプローチを使用する必要がありますか?

22
Tom

C++でオーバーロードされたコンストラクターを介して不明な型の変数を初期化する

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

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

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

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

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

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

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

17
eerorika

C++は 静的型付き言語 です。つまり、すべての変数の型は実行前に決定されます。したがって、autoキーワードは、動的に型付けされる言語であるJavaScriptのvarキーワードのようなものではありません。 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;
    }
};

このコードは、いくつかの条件が満たされている場合にコンパイルされます。たとえば、std :: ostream&に対してfunctionoperator<<を定義し、タイプTを定義する必要があります。

11
KimHajun

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

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();
6
Rhathin

_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」はコンパイル時の定数式でなければなりません。

3
Adrian Mole

autoは特定の型に推定可能である必要があります。実行時の動的型指定は提供されません。

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

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;
    }
}
1
Fire Lancer

以下の解決策は、ファイアランサーの回答の精神と似ています。主な違いは、コメントの後にテンプレートを使用している可能性があるため、インターフェイスの派生インスタンスを明示的に作成する必要がなくなることです。 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);
    }
};

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

0
jxh