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

User Defined C++11 enum class Default Constructor

提问人:FizzixNerd 提问时间:7/13/2013 最后编辑:EricFizzixNerd 更新时间:12/20/2017 访问量:46188

问:

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

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

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

我这样做而不是仅仅使用,比如说,是为了确保代码是安全的:我可以(或者以其他方式编译时确保 - 使用的实际方法对我来说并不重要)诸如某人没有犯拼写错误(传递 5 而不是 4 等)之类的事情,并且我收到类型不匹配的自动错误消息, 等。intstatic_assert

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

PinID pid = PinID();

正在获取未显式定义的枚举器(甚至在查看代码时似乎不存在),并可能导致运行时错误。这也意味着,如果没有错误/默认情况,就不可能对显式定义的枚举器的值进行处理这样的技术——这是我想避免的,因为它迫使我要么做,要么做一些事情,比如返回 a ,这不太适合静态分析。switchthrowboost::optional

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

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

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

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

我的推理中的错误是受欢迎的——在这种情况下,我开始怀疑 es 是错误工作的正确对象;如有询问,将提供澄清。感谢您抽出宝贵时间接受采访。enum class

C++ C++11 默认构造函数 枚举类

评论


答:

5赞 chrisaycock 7/13/2013 #1

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

至于你的问题,没有办法确保在编译时强制转换涉及适当的候选人。考虑:

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

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

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

评论

0赞 FizzixNerd 7/13/2013
谢谢你的回答!我知道枚举类不是一个类,但它仍然有一个默认构造函数。至于静态检查,我当然知道我不可能静态检查来自 string->enum 的转换——为此我使用 boost::optioanls,但我至少可以静态检查一些涉及 constexpr 的 enum->string,不是吗?
6赞 aschepler 7/18/2013
@FizzixNerd“有一个默认构造函数” - 从技术上讲,没有。它具有用于值初始化的已定义行为。类类型对象的值初始化调用默认构造函数;数值或枚举类型对象的值初始化将其设置为零;指针类型对象的值初始化将其设置为 null 指针值。
25赞 PeterSW 7/13/2013 #2

使用 或不是类定义的类型,而是作用域内枚举,并且不能定义默认构造函数。C++11 标准定义您的语句将给出零初始化。其中定义为 .它还允许枚举类型通常保存枚举器常量以外的值。enum classenum structPinID pid = PinID();PinIDenum 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 创建后对 PinID 的操作有多重要,是否值得编写类或只是在使用值时处理无处不在的无效值。

评论

3赞 FizzixNerd 7/15/2013
只是看这个,它看起来基本不错,但它会起作用吗?您有一个类定义,其中包含其自身作为成员的实例...哦,但它们是静态的。编译器不会生气。聪明的熊。
0赞 PeterSW 7/15/2013
@FizzixNerd 我没有通过编译器运行它,但它应该可以工作......我认为如果您添加流运算符,您应该能够直接将其与lexical_cast一起使用,我想如果出现无效值会抛出异常。
0赞 Alexander Kirov 4/24/2014
@PeterSW,您能否指出,您指的是 C++11 标准中的哪些项目,谈论零初始化?
0赞 Alexander Kirov 4/25/2014
@PeterSW谢谢!但还需要注意一点: 5.2.3(2) : 表达式 T(),其中 T 是非数组完整对象类型或(可能是 cv 限定的)void 类型的简单类型说明符或类型名称说明符,创建指定类型的 prvalue,该 prvalue 是 valueinitialized(8.5;void() 情况不进行初始化)。[ 注意:如果 T 是 cv 限定的非类类型,则在确定生成的 prvalue 的类型 (3.10) 时将忽略 cv 限定符------。
4赞 Francis Cugler 12/20/2017 #3

我知道这个问题已经过时了,并且已经有一个公认的答案,但这里有一种技术,在这种情况下可能会对 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.

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

对于这种类型,没有默认值,因为您必须通过模板参数列表使用单个或一组值实例化此类对象。如果需要,他们可以使用默认类型的单个参数。实例化此类对象时;它是最终的,因为它不能从其声明中更改。这是一个 const 对象,并且仍然是默认可构造的。specialize this class0