用户定义的C ++ 11枚举类默认构造函数

时间:2013-07-13 14:13:57

标签: c++ c++11 default-constructor enum-class

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

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

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

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

问题是enum class有一个默认的构造函数 - 为了兼容性而我假设(因为它们具有相同的行为)我认为是C enum - 初始化为{{1}相当于enum class。在这种情况下,没有0值。这意味着用户做出声明/定义如:

0

正在获取一个未明确定义的枚举器(当查看代码时甚至看起来“不存在”),并且可能导致运行时错误。这也意味着,如果没有错误/默认情况,就不可能PinID pid = PinID();明确定义的枚举器的值这样的技术 - 这是我想避免的,因为它强迫我switch或做某事比如返回一个throw,它不太适合静态分析。

我试图定义默认构造函数无济于事。我(拼命)试图定义一个共享boost::optional名称的函数,但这(不出所料)导致奇怪的编译器错误。我希望保留将enum class转换为enum class的功能,所有int枚举数都映射到各自的N#,因此仅仅“定义”,例如,N4 = 0是不可接受的;这是为了简单和理智。

我想我的问题是双重的:有没有办法在使用#之后获得静态安全性?如果没有,还有什么其他可能性?我想要的是:

  1. 是默认可构造的
  2. 可以使默认构造为任意有效值
  3. 提供enum class es
  4. 提供的“有限的指定”值
  5. 至少与enum class
  6. 一样安全
  7. (最好)不涉及运行时多态性
  8. 我想要默认可构造性的原因是因为我计划使用enum class来减少boost::lexical_cast值与我输出的实际关联enum class之间转换所涉及的语法开销到操作系统(在这种情况下是sysfs); string需要默认的可构造性。

    我的推理中的错误是受欢迎的 - 我开始怀疑boost::lexical_cast es是错误工作的正确对象,在这种情况下;如有要求,将提供澄清。谢谢你的时间。

3 个答案:

答案 0 :(得分:16)

使用enum classenum struct定义的类型不是类,而是作用域枚举,并且不能定义默认构造函数。 C ++ 11标准定义了您的PinID pid = PinID();语句将进行零初始化。将PinID定义为enum class的位置。它还允许枚举类型通常包含枚举器常量以外的值。

要理解PinID()给出零初始化需要一起读取标准部分 3.9.9,8.5.5,8.5.7 8.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在创建后的操作对于编写一个类是否值得,或者只是在使用该值时处理无效值。

答案 1 :(得分:4)

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

至于你的问题,没有办法确保在编译时,演员表涉及合适的候选人。考虑:

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

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

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

答案 2 :(得分:1)

我知道这个问题已经过时了,并且它已经有了一个公认的答案,但是这里有一种技术可能有助于在这种情况下使用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];
    }
};

静态: - 有三种方法可以写这个:(第一个--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对象,仍然可以默认构造。