42

有没有办法指定一个的默认构造函数enum class

我正在使用enum class指定一组允许库中特定数据类型的值:在这种情况下,它是 Raspberry Pi 的 GPIO 引脚 ID 号。它看起来像这样:

enum class PinID : int {N4 = 4, N17 = 17, /* ...etc... */ }

我这样做而不是仅仅使用 an 的int目的是确保代码是安全的:我可以static_assert(或者编译时确保 - 使用的实际方法对我来说并不重要)像某人没有的事情出现拼写错误(传递 5 而不是 4 等),并且我收到类型不匹配等的自动错误消息。

那么问题是它enum class有一个默认构造函数——为了与 C 的兼容性,enum我假设(因为它们具有相同的行为)——初始化enum class0. 在这种情况下,没有任何0价值。这意味着用户做出如下声明/定义:

PinID pid = PinID();

正在获取一个未明确定义的枚举器(当人们查看代码时,它甚至似乎都不“存在”),并且可能导致运行时错误。这也意味着switch在没有错误/默认情况的情况下,像对显式定义的枚举器的值进行 ing 这样的技术是不可能的——我想避免这种情况,因为它迫使我要么要么throw做类似 return a 的事情boost::optional,而这些事情不太适合静态分析。

我试图定义一个默认构造函数无济于事。我(拼命地)试图定义一个共享名称的函数enum class,但这(毫不奇怪)导致了奇怪的编译器错误。我想保留转换 to 的能力enum classint所有N#枚举器都映射到它们各自的#,所以仅仅“定义”,比如 N4 = 0 是不可接受的;这是为了简单和理智。

我想我的问题有两个:有没有办法获得我使用后的那种静态安全enum class?如果不是,人们更喜欢哪些其他可能性?我想要的是:

  1. 是默认可构造的
  2. 可以将默认构造设置为任意有效值
  3. 提供由enum classes提供的“指定的有限集”值
  4. 至少与enum class
  5. (最好)不涉及运行时多态性

我想要默认可构造性的原因是因为我计划使用它boost::lexical_cast来减少值之间转换所涉及的语法开销,以及我输出到操作系统enum class的实际关联s(在本例中为 sysfs);需要默认的可构造性。stringboost::lexical_cast

我的推理中的错误是受欢迎的——我开始怀疑enum classes 是错误工作的正确对象,在这种情况下;如果被问到,将提供澄清。感谢您的时间。

4

3 回答 3

22

使用enum classenum struct不是类定义的类型而是范围枚举,并且不能定义默认构造函数。C++11 标准定义您的PinID pid = PinID();语句将进行零初始化。WherePinID被定义为enum class. 它还允许枚举类型通常保存枚举常量以外的值。

要理解 PinID() 提供零初始化,需要一起阅读标准部分3.9.9、8.5.5、8.5.78.5.10

8.5.10 -An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized

8.5.7 - To value-initialize an object of type T means:...otherwise, the object is zero-initialized.

8.5.5 -To zero-initialize an object or reference of type T means: — if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;

3.9.9 - 枚举类型是称为标量类型的类型集的一部分的状态。

一个可能的解决方案:

为了满足您的第 1 到第 5 点,您可以按照以下方式编写一个类:

class PinID
{
private:
    PinID(int val)
    : m_value(val)
    {}

    int m_value;

public:
    static const PinID N4;
    static const PinID N17;
    /* ...etc... */ 

    PinID() 
    : m_value(N4.getValue())
    {}

    PinID(const PinID &id)
    : m_value(id.getValue())
    {}

    PinID &operator = (const PinID &rhs)
    {
        m_value = rhs.getValue();
        return *this;
    }

    int getValue() const
    {
        return m_value;
    }

    // Attempts to create from int and throw on failure.
    static PinID createFromInt(int i);

    friend std::istream& operator>>(std::istream &is, PinID &v)
    {
        int candidateVal(0);
        is >> candidateVal;
        v = PinID::createFromInt(candidateVal);
        return is;
    }
};

const PinID PinID::N4 = PinID(4);
/* ...etc... */

这可以为您提供一些您必须做出特定努力才能获得无效值的东西。默认的构造函数和流操作符应该允许它与 lexical_cast 一起工作。

似乎这取决于 PinID 在创建之后的操作有多重要,是否值得编写一个类或只是在使用值时处理无处不在的无效值。

于 2013-07-13T15:59:23.090 回答
5

Anenum class只是一个强类型的enum; 它不是一个class. C++11 只是重用了现有的class关键字,以避免引入会破坏与遗留 C++ 代码兼容性的新关键字。

至于您的问题,无法确保在编译时演员阵容涉及适当的候选人。考虑:

int x;
std::cin >> x;
auto p = static_cast<PinID>(x);

这是完全合法的,没有办法静态地确保控制台用户做了正确的事情。

相反,您需要在运行时检查该值是否有效。为了以自动化的方式解决这个问题,我的一位同事创建了一个enum生成器,该生成器在给定具有枚举值的文件的情况下构建这些检查以及其他有用的例程。您将需要找到适合您的解决方案。

于 2013-07-13T14:39:44.773 回答
4

我知道这个问题已经过时了,而且它已经有了一个可以接受的答案,但是这里有一种技术可能有助于在这种情况下使用 C++ 的一些新特性

您可以声明此类的变量non staticstatic,它可以通过当前编译器支持允许的几种方式来完成。


非静态:

#include <iostream>
#include <array>

template<unsigned... IDs>
class PinIDs {
private:
    const std::array<unsigned, sizeof...(IDs)> ids { IDs... };
public:
    PinIDs() = default;
    const unsigned& operator[]( unsigned idx ) const {
        if ( idx < 0 || idx > ids.size() - 1 ) {
            return -1;
        }
        return ids[idx];
    }
};

静态: - 有 3 种写法:(第一个 - C++11 或 14 或更高版本)最后 2 个(c++17)。

不要在 C++11 部分引用我的话;我不太确定何时首次引入可变参数模板或参数包。

template<unsigned... IDs>
class PinIDs{
private:        
    static const std::array<unsigned, sizeof...(IDs)> ids;
public:    
    PinIDs() = default;    
    const unsigned& operator[]( unsigned idx ) const {
        if ( idx < 0 || idx > ids.size() - 1 ) {
            return -1;
        }
        return ids[idx];
    }
};

template<unsigned... IDs>
const std::array<unsigned, sizeof...(IDs)> PinIDs<IDs...>::ids { IDs... };

template<unsigned... IDs>
class PinIDs{
private:
    static constexpr std::array<unsigned, sizeof...(IDs)> ids { IDs... }; 
public:   
    PinIDs() = default;    
    const unsigned& operator[]( unsigned idx ) const {
        if ( idx < 0 || idx > ids.size() - 1 ) {
            return -1;
        }
        return ids[idx];
    }
};

template<unsigned... IDs>
class PinIDs{
private:
    static inline const std::array<unsigned, sizeof...(IDs)> ids { IDs... };
public:    
    PinIDs() = default;    
    const unsigned& operator[]( unsigned idx ) const {
        if ( idx < 0 || idx > ids.size() - 1 ) {
            return -1;
        }
        return ids[idx];
    }
};

上面的所有非静态或静态示例都适用于以下相同的用例并提供正确的结果:

int main() {
    PinIDs<4, 17, 19> myId;

    std::cout << myId[0] << " ";
    std::cout << myId[1] << " ";
    std::cout << myId[2] << " ";

    std::cout << "\nPress any key and enter to quit." << std::endl;
    char c;
    std::cin >> c;

    return 0;
}

输出

4 17 19
Press any key and enter to quit.

使用这种使用可变参数列表的类模板,您不必使用任何构造函数,但默认值。我确实在数组中添加了边界检查,以便operator[]不超过其大小的范围;我可以抛出一个错误,但unsigned我只是简单地将 -1 作为无效值返回。

使用这种类型,没有默认值,因为您必须通过模板参数列表使用单个或一组值来实例化这种对象。如果有人愿意,他们可以specialize this class使用单个参数0作为默认类型。当您实例化此类对象时;它是最终的,因为它不能从它的声明中改变。这是一个 const 对象,仍然是默认可构造的。

于 2017-12-20T08:07:38.743 回答