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

问题描述 投票:28回答:3

有没有办法指定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 - enums - 初始化为enum class相当于0。在这种情况下,没有0值。这意味着用户做出如下声明/定义:

PinID pid = PinID();

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

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

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

  1. 是默认构造的
  2. 可以使默认构造为任意有效值
  3. 提供enum classes提供的“有限的指定”值
  4. 至少和enum class一样安全
  5. (最好)不涉及运行时多态性

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

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

c++ c++11 default-constructor enum-class
3个回答
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:

05/08/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在创建后的操作对于编写类或者只是在使用值时处理无效值是多么重要。


4
投票

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

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

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

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

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


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

静态: - 有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对象,仍然可以默认构造。

© www.soinside.com 2019 - 2024. All rights reserved.