Khởi tạo một biến kiểu không xác định thông qua các hàm tạo quá tải trong C ++


22

đến từ một nền tảng chủ yếu là python Tôi đã phải vật lộn với việc làm việc với các loại trong C ++.

Tôi đang cố gắng khởi tạo một biến lớp thông qua một trong số các hàm tạo bị quá tải, lấy các loại khác nhau làm tham số. Tôi đã đọc được rằng sử dụng autotừ khóa có thể được sử dụng để tự động khai báo một biến, tuy nhiên trong trường hợp của tôi, nó sẽ không được khởi tạo cho đến khi một hàm tạo được chọn. Tuy nhiên, trình biên dịch không hài lòng về việc không khởi tạo 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;
    }
};

Trong python điều này có thể trông giống như:

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

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

Cách sử dụng autotừ khóa đúng trong kịch bản này là gì? Tôi có nên sử dụng một cách tiếp cận khác nhau hoàn toàn?


2
Tôi tin rằng bạn không thể sử dụng autocho các thành viên lớp học? Câu hỏi có liên quan nhưng đã lỗi thời: Có thể có biến thành viên tự động của ED không?
Yksisarvinen

Bất kỳ lý do không sử dụng mẫu?
Jimmy RT

Với python, các loại được xác định trên mỗi thao tác trong thời gian chạy - yêu cầu phí, nhưng cho phép các loại biến thay đổi từ một câu lệnh sang câu lệnh tiếp theo. Trong C ++, các loại cần phải được biết trước, để mã có thể biên dịch - float và int có bố cục nhị phân khác nhau và yêu cầu các hướng dẫn lắp ráp khác nhau để làm việc. Nếu bạn muốn linh hoạt trong thời gian chạy, bạn cần sử dụng loại kết hợp, chẳng hạn như biến thể chọn một trong nhiều nhánh chứa mã hợp lệ cho mỗi loại, thêm chi phí hiệu năng. Nếu bạn muốn tách riêng phiên bản int và float, các mẫu là bạn của bạn.
Jake

Câu trả lời:


17

Khởi tạo một biến kiểu không xác định thông qua các hàm tạo quá tải trong C ++

Không có thứ gọi là "biến không xác định" trong C ++.

Cách sử dụng đúng từ khóa tự động trong kịch bản này là gì?

các biến tự động suy ra có một loại được suy ra từ bộ khởi tạo. Nếu không có trình khởi tạo, thì bạn không thể sử dụng tự động. tự động không thể được sử dụng cho một biến thành viên không tĩnh. Một thể hiện của một lớp không thể có các thành viên gõ khác với các thể hiện khác.

Không có cách nào sử dụng từ khóa tự động trong kịch bản này.

Tôi có nên sử dụng một cách tiếp cận khác nhau hoàn toàn?

Có lẽ. Có vẻ như bạn đang cố gắng thực hiện a std::variant. Nếu bạn cần một biến để lưu trữ một trong số X loại, đó là những gì bạn nên sử dụng.

Tuy nhiên, bạn có thể đang cố gắng mô phỏng kiểu gõ động trong C ++. Mặc dù nó có thể quen thuộc với bạn do có kinh nghiệm với Python, nhưng trong nhiều trường hợp đó không phải là phương pháp lý tưởng. Ví dụ, trong chương trình ví dụ cụ thể này, tất cả những gì bạn làm với biến thành viên là in nó. Vì vậy, sẽ đơn giản hơn để lưu trữ một chuỗi trong mỗi trường hợp. Các cách tiếp cận khác là đa hình tĩnh như được thể hiện bởi đa hình động kiểu Rhathin hoặc OOP như được chỉ ra bởi Fire Lancer.


Sử dụng công đoàn cũng đủ điều kiện trong trường hợp này?
wonderra

unionlà một cơ chế cấp thấp dễ bị lỗi. variantcó thể sử dụng nó trong nội bộ và làm cho việc sử dụng nó an toàn hơn.
Erlkoenig

@wondra union tự nó sẽ không hữu ích vì nó không thể được kiểm tra xem thành viên nào đang hoạt động. Nó cũng rất đau khi sử dụng với các lớp không tầm thường (có hàm hủy tùy chỉnh) như std :: string. Những gì người ta muốn là một liên minh được gắn thẻ . Đó là cơ sở hạ tầng mà std :: biến thể thực hiện.
eerorika

1
libstdc ++ variant không sử dụng union. Cách khác, sử dụng bộ nhớ thô và vị trí mới, không thể được sử dụng trong hàm constexprtạo.
Erlkoenig

@Erlkoenig đủ công bằng, tôi lấy lại những gì tôi nói. Tôi chỉ xem xét việc tăng cường triển khai, không sử dụng liên minh và cho rằng mọi người đều làm như vậy.
eerorika

11

C ++ là một ngôn ngữ gõ tĩnh , có nghĩa là tất cả các loại biến được xác định trước khi chạy. Do đó, autotừ khóa không phải là một cái gì đó giống như vartừ khóa trong javascript, đây là một ngôn ngữ được gõ động. autotừ khóa thường được sử dụng để xác định các loại phức tạp không cần thiết.

Thay vào đó, những gì bạn đang tìm kiếm có thể được thực hiện bằng cách sử dụng lớp mẫu C ++, cho phép tạo nhiều phiên bản của lớp có các loại khác nhau.

Mã này có thể là câu trả lời bạn đang tìm kiếm.

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

Mã này sẽ biên dịch nếu một số điều kiện được đáp ứng, như hàm operator<<nên được xác định cho std :: ostream & và loại T.


6

Cách tiếp cận khác nhau, hơn những gì người khác đã đề xuất, là sử dụng các mẫu. Đây là một ví dụ:

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

Sau đó, bạn có thể sử dụng lớp của bạn như thế này:

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

3

Bạn có thể sử dụng các std::variantloại. Mã dưới đây cho thấy một cách (nhưng nó hơi vụng về, tôi phải thừa nhận):

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

Sẽ tốt hơn nhiều nếu std::get<0>(value)có thể được viết là std::get<value.index()>(value)nhưng, than ôi, "x" trong <x>phải là một biểu thức hằng số thời gian biên dịch.


1
Có lẽ tốt hơn để sử dụng std::visitthay vì switch.
eerorika

1

auto phải được khấu trừ cho một loại cụ thể, nó không cung cấp kiểu gõ động thời gian chạy.

Nếu tại thời điểm khai báo Tokenbạn biết tất cả các loại có thể bạn có thể sử dụng, std::variant<Type1, Type2, Type3>v.v ... Điều này tương tự như có "loại enum" và "hợp nhất". Nó đảm bảo các hàm tạo và hàm hủy thích hợp được gọi.

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

Một cách khác có thể là tạo một Tokenkiểu con khác nhau cho từng trường hợp (có thể sử dụng các mẫu) với các phương thức ảo phù hợp.

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

Giải pháp dưới đây tương tự như câu trả lời trong câu trả lời của Fire Lancer. Điểm khác biệt chính của nó là nó tuân theo nhận xét có thể sử dụng các mẫu và do đó loại bỏ sự cần thiết phải tạo ra các phiên bản dẫn xuất của giao diện một cách rõ ràng. Tokenbản thân nó không phải là lớp giao diện. Thay vào đó, nó định nghĩa giao diện là một lớp bên trong và một lớp mẫu bên trong để tự động hóa định nghĩa của các lớp dẫn xuất.

Định nghĩa của nó có vẻ quá phức tạp. Tuy nhiên, Token::Basexác định giao diện và Token::Impl<>xuất phát từ giao diện. Các lớp bên trong này hoàn toàn ẩn cho người dùng Token. Việc sử dụng sẽ như thế nào:

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

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

Ngoài ra, giải pháp bên dưới minh họa cách người ta có thể triển khai toán tử chuyển đổi để gán một Tokenthể hiện cho một biến thông thường. Nó dựa vào dynamic_cast, và sẽ ném ngoại lệ nếu dàn diễn viên không hợp lệ.

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

Định nghĩa của Tokendưới đây.

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

Hãy thử trực tuyến!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.