การเริ่มต้นตัวแปรชนิดที่ไม่รู้จักผ่านตัวสร้างที่โอเวอร์โหลดใน C ++


22

มาจากพื้นหลังหลามส่วนใหญ่ฉันค่อนข้างลำบากกับการทำงานกับประเภทใน C ++

ฉันพยายามที่จะเริ่มต้นตัวแปรระดับผ่านหนึ่งในหลายตัวสร้างมากเกินไปที่ใช้ประเภทที่แตกต่างกันเป็นพารามิเตอร์ ฉันได้อ่านแล้วว่าการใช้autoคำหลักนั้นสามารถใช้สำหรับการประกาศอัตโนมัติของตัวแปรได้อย่างไรก็ตามในกรณีของฉันมันจะไม่ถูกกำหนดค่าเริ่มต้นจนกว่าจะเลือกคอนสตรัคเตอร์ แต่คอมไพเลอร์ไม่ได้มีความสุขเกี่ยวกับการไม่ valueinitialising

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

ในไพ ธ อนนี้อาจมีลักษณะดังนี้:

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

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

วิธีที่ถูกต้องในการใช้autoคำหลักในสถานการณ์นี้คืออะไร? ฉันควรใช้วิธีการที่แตกต่างกันโดยสิ้นเชิงไหม


2
ฉันเชื่อว่าคุณไม่สามารถใช้autoสำหรับสมาชิกชั้นเรียนได้ทั้งหมด? คำถามที่เกี่ยวข้อง แต่ล้าสมัย: เป็นไปได้ไหมที่จะมีตัวแปรสมาชิก“ auto”?
Yksisarvinen

มีเหตุผลใดที่จะไม่ใช้เทมเพลต?
Jimmy RT

ด้วยไพ ธ อนประเภทจะถูกกำหนดในแต่ละการดำเนินการที่รันไทม์ - ซึ่งต้องการค่าใช้จ่าย แต่อนุญาตให้ตัวแปรประเภทเปลี่ยนจากคำสั่งหนึ่งไปเป็นคำสั่งถัดไป ใน C ++ จำเป็นต้องทราบชนิดล่วงหน้าเพื่อให้โค้ดสามารถคอมไพล์ - float และ int มีรูปแบบไบนารีที่แตกต่างกันและต้องการคำแนะนำการประกอบที่แตกต่างกันเพื่อให้ทำงานได้ หากคุณต้องการความยืดหยุ่นในการใช้งานจริงคุณต้องใช้ประเภทยูเนี่ยนเช่นชุดตัวเลือกที่เลือกหนึ่งในหลายสาขาที่มีรหัสที่ถูกต้องสำหรับแต่ละประเภทเพิ่มค่าใช้จ่ายด้านประสิทธิภาพ หากคุณต้องการแยกรุ่น int และ float แยกออกแม่แบบคือเพื่อนของคุณ
Jake

คำตอบ:


17

การเริ่มต้นตัวแปรชนิดที่ไม่รู้จักผ่านตัวสร้างที่โอเวอร์โหลดใน C ++

ไม่มีสิ่งเช่น "ตัวแปรประเภทที่ไม่รู้จัก" ใน C ++

วิธีที่ถูกต้องในการใช้คำหลักอัตโนมัติในสถานการณ์นี้คืออะไร?

ตัวแปร auto-deduced มีชนิดที่อนุมานจาก initializer หากไม่มีผู้เริ่มต้นคุณจะไม่สามารถใช้งานอัตโนมัติได้ อัตโนมัติไม่สามารถใช้สำหรับตัวแปรสมาชิกแบบคงที่ อินสแตนซ์หนึ่งของคลาสไม่สามารถมีสมาชิกที่พิมพ์แตกต่างจากอินสแตนซ์อื่น

ไม่มีวิธีใช้คำหลักอัตโนมัติในสถานการณ์นี้

ฉันควรใช้วิธีการที่แตกต่างกันโดยสิ้นเชิงไหม

อาจ. ดูเหมือนว่าคุณกำลังพยายามที่จะใช้std::variantดูเหมือนว่าคุณกำลังพยายามที่จะดำเนินการหากคุณต้องการตัวแปรในการจัดเก็บหนึ่งในประเภทจำนวน X นั่นคือสิ่งที่คุณควรใช้

อย่างไรก็ตามคุณอาจลองจำลองการพิมพ์แบบไดนามิกใน C ++ ในขณะที่คุณอาจคุ้นเคยกับการใช้ Python ในหลาย ๆ กรณีที่ไม่ใช่วิธีที่ดีที่สุด ตัวอย่างเช่นในโปรแกรมตัวอย่างนี้สิ่งที่คุณทำกับตัวแปรสมาชิกจะถูกพิมพ์ ดังนั้นการเก็บสตริงในแต่ละกรณีจะง่ายกว่า วิธีอื่น ๆ นั้นคือpolymorphism แบบคงที่ตามที่แสดงโดย Rhathin หรือ OOP style polymorphism แบบไดนามิกที่แสดงโดย Lancer ไฟ


การใช้สหภาพจะมีคุณสมบัติในกรณีนี้ด้วยหรือไม่
wondra

unionเป็นกลไกระดับต่ำที่ผิดพลาดได้ง่าย variantอาจใช้ภายในและทำให้การใช้ปลอดภัยยิ่งขึ้น
Erlkoenig

@wondra union ด้วยตัวเองจะไม่เป็นประโยชน์มากเพราะมันไม่สามารถตรวจสอบได้สำหรับสมาชิกที่กำลังใช้งานอยู่ นอกจากนี้ยังเจ็บปวดมากที่จะใช้กับคลาสที่ไม่สำคัญ (ซึ่งมี destructor ที่กำหนดเอง) เช่น std :: string สิ่งที่ต้องการคือสหภาพที่ติดแท็ก ซึ่งเป็นโครงสร้างข้อมูลที่ std :: ตัวแปรดำเนินการ
eerorika

1
libstdc ++ 's variant ไม่unionใช้ ทางเลือกโดยใช้หน่วยความจำแบบดิบและตำแหน่งใหม่ไม่สามารถใช้ในconstexprConstructor ได้
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 & และ type 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ประเภท รหัสด้านล่างแสดงทางเดียว (แต่มันค่อนข้างงุ่มง่ามฉันต้องยอมรับ):

#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>เป็นต้นซึ่งคล้ายกับการมี "ประเภท enum" และ "สหภาพ" ทำให้แน่ใจว่ามีการเรียก constructors และ destructors ที่เหมาะสม

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.