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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17630967/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 21:18:51  来源:igfitidea点击:

User Defined C++11 enum class Default Constructor

c++c++11default-constructorenum-class

提问by FizzixNerd

Is there a way to specify the default constructor of an enum class?

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

I am using an enum classto specify a set of values which are allowable for a particular datatype in a library: in this case, it's the GPIO pin id numbers of a Raspberry Pi. It looks something like this:

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

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

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

The point of me doing this instead just of using, say, an intis to ensure that code is safe: I can static_assert(or otherwise compile-time ensure -- the actual method used is not important to me) things like that someone hasn't made a spelling error (passing a 5 instead of a 4, etc), and I get automatic error messages for type mismatches, etc.

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

The problem then is that enum classhas a default constructor that -- for compatibility's sake with C's enums I assume (since they have the same behaviour) -- initializes to the enum classequivalent of 0. In this case, there is no 0value. This means that a user making a declaration/definition like:

接着的问题是,enum class有一个默认的构造函数-对于兼容性的缘故使用C的enum的I假设(因为它们具有相同的行为) -初始化为enum class等效0。在这种情况下,没有任何0价值。这意味着用户做出如下声明/定义:

PinID pid = PinID();

PinID pid = PinID();

is getting an enumerator that isn't explicitly defined (and doesn't even seem to "exist" when one looks at the code), and can lead to runtime errors. This also means that techniques like switching over the values of explicitly defined enumerators is impossible without having an error/default case -- something I want to avoid, since it forces me to either throwor do something like return a boost::optional, which are less amenable to static analysis.

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

I tried to define a default constructor to no avail. I (desperately) tried to define a function which shares the name of the enum class, but this (rather unsurprisingly) resulted in strange compiler errors. I want to retain the ability to cast the enum classto int, with all N#enumerators mapping to their respective #, so merely "defining", say, N4 = 0 is unacceptable; this is for simplicity and sanity.

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

I guess my question is two-fold: is there a way to get the kind of static safety I'm after using enum class? If not, what other possibilities would one prefer? What I want is something which:

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

  1. is default constructable
  2. can be made to default construct to an arbitrary valid value
  3. provides the "finite set of specified" values afforded by enum classes
  4. is at least as type safe as an enum class
  5. (preferably) doesn't involve runtime polymorphism
  1. 是默认可构造的
  2. 可以将默认构造设置为任意有效值
  3. 提供enum classes提供的“指定的有限集”值
  4. 至少是类型安全的 enum class
  5. (最好)不涉及运行时多态性

The reason I want default constructability is because I plan to use boost::lexical_castto reduce the syntactic overhead involved in conversions between the enum classvalues, and the actual associated strings which I output to the operating system (sysfs in this case); boost::lexical_castrequires default constructability.

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

Errors in my reasoning are welcome -- I am beginning to suspect that enum classes are the right object for the wrong job, in this case; clarification will be offered if asked. Thank you for your time.

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

采纳答案by PeterSW

A type defined with enum classor enum structis not a a class but a scoped enumeration and can not have a default constructor defined. The C++11 standard defines that your PinID pid = PinID();statement will give a zero-initialization. Where PinIDwas defined as a enum class. It also allows enum types in general to hold values other than the enumerator constants.

enum class或定义的类型enum struct不是类而是作用域枚举,并且不能定义默认构造函数。C++11 标准定义您的PinID pid = PinID();语句将给出零初始化。其中PinID定义为enum class. 它还允许枚举类型通常保存枚举常量以外的值。

To understand that PinID() gives zero initialization requires reading standard sections 3.9.9, 8.5.5, 8.5.7and 8.5.10together:

要理解 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.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.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;

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- States that enumeration types are part of the set of types known as scalar types.

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

A possible solution:

一个可能的解决方案:

To meet your points 1 to 5 you could write a class along the lines of:

为了满足您的第 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... */

That can give you something that you would have to make specific efforts to get an invalid values into. The default constructor and stream operator should allow it to work with lexical_cast.

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

Seems it depends how critical the operations on a PinID are after it's creation whether it's worth writing a class or just handling the invalid values everywhere as the value is used.

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

回答by chrisaycock

An enum classis just a strongly-typed enum; it's not a class. C++11 just reused the existing classkeyword to avoid introducing a new keyword that would break compatibility with legacy C++ code.

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

As for your question, there is no way to ensure at compile timethat a cast involves a proper candidate. Consider:

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

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

This is perfectly legal and there is no way to statically ensure the console user has done the right thing.

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

Instead, you will need to check at runtimethat the value is valid. To get around this in an automated fashion, one of my co-workers created an enumgenerator that builds these checks plus other helpful routines given a file with enumeration values. You will need to find a solution that works for you.

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

回答by Francis Cugler

I know that this question is dated and that it already has an accepted answer but here is a technique that might help in a situation like this with some of the newer features of C++

我知道这个问题已经过时了,并且它已经有一个公认的答案,但这里有一种技术可能有助于在这种情况下使用 C++ 的一些新功能

You can declare this class's variable either non staticor static, it can be done in several ways permitted on support of your current compiler.

您可以声明此类的变量,non static或者static,可以通过支持当前编译器的多种方式来完成。



Non Static:

非静态:

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


Static:- There are 3 ways to write this: (First One - C++11 or 14 or higher) last 2 (c++17).

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

Don't quote me on the C++11 part; I'm not quite sure when variadic templates or parameter packs were first introduced.

不要在 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];
    }
};


All examples above either non-static or static work with the same use case below and provide the correct results:

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

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


Output

输出

4 17 19
Press any key and enter to quit.


With this type of class template using a variadic parameter list, you don't have to use any constructor but the default. I did add bounds checking into the array so that the operator[]doesn't exceed bounds of its size; I could of threw an error but with unsignedtype I just simply returned -1 as an invalid value.

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

With this type, there is no default as you have to instantiate this kind of object via template parameter list with a single or set of values. If one wants to they can specialize this classwith a single parameter of 0for a default type. When you instantiate this type of object; it is final as in it can not be changed from its declaration. This is a const object and still holds to be default constructible.

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