通过C ++中的重载构造函数初始化未知类型的变量


22

来自主要来自python的背景,我在使用C ++类型方面有些挣扎。

我试图通过几个采用不同类型作为参数的重载构造函数之一来初始化类变量。我已经读到使用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具有不同的二进制布局,并且需要使用不同的汇编指令。如果要在运行时具有灵活性,则需要使用联合类型(例如,variant),该联合类型会为许多类型选择包含有效代码的多个分支之一,从而增加了性能开销。如果要将int和float版本分开,则模板是您的朋友。
杰克

Answers:


17

通过C ++中的重载构造函数初始化未知类型的变量

在C ++中没有“未知类型的变量”这样的东西。

在这种情况下,使用auto关键字的正确方法是什么?

自动推导的变量具有从初始化程序推导的类型。如果没有初始化程序,则不能使用自动。auto不能用于非静态成员变量。类的一个实例不能具有与另一实例不同类型的成员。

在这种情况下,无法使用自动关键字。

我是否应该完全使用其他方法?

大概。看来您正在尝试实现std::variant。如果您需要一个变量来存储X个类型之一,那就应该使用它。

但是,您可能正在尝试在C ++中模拟动态类型。尽管您可能会因使用Python而熟悉,但在许多情况下,这不是理想的方法。例如,在这个特定的示例程序中,您对member变量所做的所有事情都将其打印出来。因此,在每种情况下存储一个字符串将更为简单。其他方法是如Rhathin所示的静态多态性或Fire Lancer所示的OOP风格的动态多态性


在这种情况下,使用工会是否也有资格?
温德拉

union是易于出错的低级机制。variant可能在内部使用它并使其使用更安全。
Erlkoenig '19

@wondra union本身并不是很有用,因为无法检查当前哪个成员处于活动状态。与非平凡的类(具有自定义析构函数)(例如std :: string)一起使用也非常痛苦。想要的是带标签的工会。这是std :: variant实现的数据结构。
eerorika '19

1
libstdc ++ variant 确实使用union。使用原始内存并放置new的替代方法不能在constexpr构造函数中使用。
Erlkoenig

@Erlkoenig足够公平,我收回我说的话。我只研究了不使用联合的boosts实现,并假设每个人都做同样的事情。
eerorika '19

11

C ++是一种静态类型的语言,这意味着所有变量类型都是在运行时确定的。因此,auto关键字不同于varjavascript(一种动态类型的语言)中的关键字。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类型。下面的代码显示了一种方法(但我必须承认这有点笨拙):

#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 '19

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

下面的解决方案在本质上类似于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);
    }
};

在线尝试!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.