Menginisialisasi variabel tipe tidak dikenal melalui konstruktor kelebihan beban di C ++

22

berasal dari latar belakang terutama python Saya agak kesulitan bekerja dengan tipe C ++.

Saya mencoba menginisialisasi variabel kelas melalui salah satu dari beberapa konstruktor kelebihan beban yang menggunakan berbagai jenis sebagai parameter. Saya telah membaca bahwa menggunakan autokata kunci dapat digunakan untuk deklarasi otomatis suatu variabel, namun dalam kasus saya ini tidak akan diinisialisasi sampai konstruktor dipilih. Namun kompiler tidak senang tidak menginisialisasi 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;
    }
};

Dalam python ini mungkin terlihat seperti:

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

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

Apa cara yang benar untuk menggunakan autokata kunci dalam skenario ini? Haruskah saya menggunakan pendekatan yang berbeda sama sekali?

Tom
sumber
2
Saya percaya Anda tidak dapat menggunakan autoanggota kelas sama sekali? Pertanyaan yang relevan tetapi kedaluwarsa: Apakah mungkin untuk memiliki variabel anggota "otomatis"?
Yksisarvinen
Ada alasan untuk tidak menggunakan templat?
Jimmy RT
Dengan python, tipe ditentukan pada setiap operasi saat runtime - yang membutuhkan overhead, tetapi memungkinkan tipe variabel untuk berubah dari satu pernyataan ke yang berikutnya. Dalam C ++ jenis perlu diketahui terlebih dahulu, sehingga kode dapat dikompilasi - float dan int memiliki tata letak biner yang berbeda dan memerlukan instruksi perakitan yang berbeda untuk bekerja dengannya. Jika Anda menginginkan fleksibilitas saat runtime, Anda perlu menggunakan tipe gabungan seperti varian yang memilih salah satu dari banyak cabang yang berisi kode yang valid untuk setiap jenis, menambahkan overhead kinerja. Jika Anda ingin memisahkan versi int dan float, templat adalah teman Anda.
Jake

Jawaban:

17

Menginisialisasi variabel tipe tidak dikenal melalui konstruktor kelebihan beban di C ++

Tidak ada yang namanya "variabel tipe tidak dikenal" di C ++.

Apa cara yang tepat untuk menggunakan kata kunci otomatis dalam skenario ini?

variabel yang dideduksi otomatis memiliki tipe yang dideduksi dari initialiser. Jika tidak ada penginisialisasi, maka Anda tidak dapat menggunakan otomatis. otomatis tidak dapat digunakan untuk variabel anggota yang tidak statis. Satu instance kelas tidak dapat memiliki anggota yang diketik berbeda dari instance lain.

Tidak ada cara menggunakan kata kunci otomatis dalam skenario ini.

Haruskah saya menggunakan pendekatan yang berbeda sama sekali?

Mungkin. Sepertinya Anda mencoba menerapkan std::variant. Jika Anda memerlukan variabel untuk menyimpan salah satu tipe X, itulah yang harus Anda gunakan.

Namun, Anda mungkin mencoba meniru pengetikan dinamis dalam C ++. Meskipun mungkin sudah tidak asing bagi Anda karena pengalaman dengan Python, dalam banyak kasus itu bukan pendekatan yang ideal. Misalnya, dalam program contoh khusus ini, semua yang Anda lakukan dengan variabel anggota adalah mencetaknya. Jadi akan lebih mudah untuk menyimpan string di setiap case. Pendekatan lain adalah polimorfisme statis seperti yang ditunjukkan oleh Rhathin atau polimorfisme dinamis gaya OOP seperti yang ditunjukkan oleh Fire Lancer.

eerorika
sumber
Apakah menggunakan serikat pekerja juga memenuhi syarat dalam hal ini?
keajaiban
unionadalah mekanisme tingkat rendah yang rawan kesalahan. variantmungkin menggunakannya secara internal dan membuat penggunaannya lebih aman.
Erlkoenig
@wondra union dengan sendirinya tidak akan sangat berguna karena tidak dapat diperiksa untuk anggota yang saat ini aktif. Juga sangat menyakitkan untuk digunakan dengan kelas yang tidak sepele (yang memiliki custom destructor) seperti std :: string. Yang diinginkan adalah serikat yang ditandai . Yang merupakan struktur dat yang std :: mengimplementasikan varian.
eerorika
1
libstdc ++ variant tidak digunakan union. Alternatifnya, menggunakan memori mentah dan penempatan baru, tidak dapat digunakan dalam constexprkonstruktor.
Erlkoenig
@ Erlkoenig cukup adil, saya mengambil kembali apa yang saya katakan. Saya hanya melihat implementasi boost, yang tidak menggunakan union, dan berasumsi bahwa semua orang melakukan hal yang sama.
eerorika
11

C ++ adalah bahasa yang diketik secara statis , artinya semua tipe variabel ditentukan sebelum runtime. Oleh karena itu, autokata kunci bukanlah sesuatu seperti varkata kunci dalam javascript, yang merupakan bahasa yang diketik secara dinamis. autokata kunci biasanya digunakan untuk menentukan jenis yang tidak perlu rumit.

Apa yang Anda cari mungkin dilakukan dengan menggunakan kelas template C ++, yang memungkinkan pembuatan beberapa versi kelas yang menggunakan tipe berbeda.

Kode ini mungkin jawaban yang Anda cari.

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

Kode ini akan dikompilasi jika beberapa kondisi terpenuhi, seperti fungsi operator<<harus didefinisikan untuk std :: ostream & dan ketik T.

KimHajun
sumber
6

Pendekatan yang berbeda, dari apa yang diusulkan orang lain, adalah menggunakan templat. Berikut ini sebuah contoh:

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

Maka Anda dapat menggunakan kelas Anda seperti ini:

Token<int> x(5);
x.printValue();
Rhathin
sumber
3

Anda bisa menggunakan std::varianttipe tersebut. Kode di bawah ini menunjukkan satu cara (tapi agak canggung, saya harus akui):

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

Akan jauh lebih baik jika std::get<0>(value)bisa ditulis sebagai std::get<value.index()>(value)tetapi, sayangnya, "x" di <x>harus menjadi ekspresi konstan waktu kompilasi.

Adrian Mole
sumber
1
Mungkin lebih baik digunakan std::visitdaripada switch.
eerorika
1

auto harus dapat dikurangkan ke jenis tertentu, itu tidak menyediakan mengetik dinamis runtime.

Jika pada saat menyatakan TokenAnda tahu semua tipe yang mungkin dapat Anda gunakan, std::variant<Type1, Type2, Type3>dll. Ini mirip dengan memiliki "tipe enum" dan "gabungan". Itu memastikan konstruktor dan destruktor yang tepat dipanggil.

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

Alternatifnya adalah membuat Tokensubtipe yang berbeda untuk setiap kasus (mungkin menggunakan templat) dengan metode virtual yang sesuai.

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;
    }
}
Lancer Api
sumber
0

Solusi di bawah ini serupa dalam semangat dengan yang ada di jawaban Fire Lancer. Perbedaan utamanya adalah mengikuti komentar yang mungkin menggunakan templat , dan karenanya menghilangkan kebutuhan untuk secara eksplisit membuat turunan instance dari antarmuka. Tokenbukan kelas antarmuka itu sendiri. Alih-alih, ia mendefinisikan antarmuka sebagai kelas dalam, dan kelas templat dalam untuk mengotomatiskan definisi kelas turunan.

Definisi itu tampak terlalu rumit. Namun, Token::Basemendefinisikan antarmuka, dan Token::Impl<>berasal dari antarmuka. Kelas-kelas dalam ini sepenuhnya tersembunyi bagi pengguna Token. Penggunaannya akan terlihat seperti:

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

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

Juga, solusi di bawah ini mengilustrasikan bagaimana seseorang dapat mengimplementasikan operator konversi untuk menetapkan Tokeninstance ke variabel reguler. Ini bergantung pada dynamic_cast, dan akan melemparkan pengecualian jika para pemain tidak valid.

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

Definisi di Tokenbawah ini.

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

Cobalah online!

jxh
sumber