结合C++11的新特性来解析C++中的枚举与联合

枚举
枚举是用户定义的类型,其中包含一组称为枚举器的命名的整型常数。
语法

// unscoped enum:
enum [identifier] [: type]

{enum-list}; 

// scoped enum:
enum [class|struct]
[identifier] [: type]
{enum-list};
// Forward declaration of enumerations (C++11):
enum A : int; // non-scoped enum must have type specified
enum class B; // scoped enum defaults to int
enum class C : short;

参数
identifier
指定给与枚举的类型名称。
type
枚举器的基础类型;所有枚举器都具有相同的基础类型。可能是任何整型。
enum-list
枚举中以逗号分隔的枚举器列表。范围中的每个枚举器或变量名必须是唯一的。但是,值可以重复。在未区分范围的枚举中,范围是周边范围;在区分范围的枚举中,范围是 enum-list 本身。
class
可使用声明中的此关键字指定枚举区分范围,并且必须提供 identifier。还可使用 struct 关键字来代替 class,因为在此上下文中它们在语义上等效。
备注
枚举提供上下文来描述以命名常数表示的一系列值,这些值也称为枚举器。在原始 C 和 C++ 枚举类型中,非限定枚举器在声明枚举的整个范围中可见。在区分范围的枚举中,枚举器名称必须由枚举类型名称限定。以下示例演示两种枚举之间的基本差异:

namespace CardGame_Scoped
{
  enum class Suit { Diamonds, Hearts, Clubs, Spades };

  void PlayCard(Suit suit)
  {
    if (suit == Suit::Clubs) // Enumerator must be qualified by enum type
    { /*...*/}
  }
}

namespace CardGame_NonScoped
{
  enum Suit { Diamonds, Hearts, Clubs, Spades };

  void PlayCard(Suit suit)
  {
    if (suit == Clubs) // Enumerator is visible without qualification
    { /*...*/
    }
  }
}

将为枚举中的每个名称分配一个整数值,该值与其在枚举中的顺序相对应。默认情况下,为第一个值分配 0,为下一个值分配 1,以此类推,但你可以显式设置枚举器的值,如下所示:

enum Suit { Diamonds = 1, Hearts, Clubs, Spades };

为枚举器 Diamonds 分配值 1。后续枚举器接收的值会在前一个枚举器的值的基础上加一(如果没有显式赋值)。在前面的示例中,Hearts 将具有值 2,Clubs 将具有值 3,依此类推。
每个枚举器将被视为常数,并且必须在定义 enum 的范围内(对于未区分围的枚举)或在枚举本身中(对于区分范围的枚举)具有唯一名称。为这些名称指定的值不必是唯一的。例如,如果一个未区分范围的枚举 Suit 的声明如下:

enum Suit { Diamonds = 5, Hearts, Clubs = 4, Spades };

Diamonds、Hearts、Clubs 和 Spades 的值分别是 5、6、4 和 5。请注意,5 使用了多次;尽管这并不符合预期,但是允许的。对于区分范围的枚举来说,这些规则是相同的。
强制转换规则
未区分范围的枚举常数可以隐式转换为 int,但是 int 不可以隐式转换为枚举值。下面的示例显示了如果尝试为 hand 分配一个不是 Suit 的值可能出现的情况:

int account_num = 135692;
Suit hand;
hand = account_num; // error C2440: '=' : cannot convert from 'int' to 'Suit'

将 int 转换为区分范围或未区分范围的枚举器时,需要强制转换。但是,你可以将区分范围的枚举器提升为整数值,而不进行强制转换。

int account_num = Hearts; //OK if Hearts is in a unscoped enum

按照这种方式使用隐式转换可能导致意外副作用。若要帮助消除与区分范围的枚举相关的编程错误,区分范围的枚举值必须是强类型值。区分范围的枚举器必须由枚举类型名称(标识符)限定,并且无法进行隐式转换,如以下示例所示:

namespace ScopedEnumConversions
{
  enum class Suit { Diamonds, Hearts, Clubs, Spades };

  void AttemptConversions()
  {
    Suit hand;
    hand = Clubs; // error C2065: 'Clubs' : undeclared identifier
    hand = Suit::Clubs; //Correct.
    int account_num = 135692;
    hand = account_num; // error C2440: '=' : cannot convert from 'int' to 'Suit'
    hand = static_cast<Suit>(account_num); // OK, but probably a bug!!!

    account_num = Suit::Hearts; // error C2440: '=' : cannot convert from 'Suit' to 'int'
    account_num = static_cast<int>(Suit::Hearts); // OK
}

注意,hand = account_num; 行仍会导致对未区分范围的枚举发生的错误,如前面所示。它可以与显式强制转换一起使用。但是,借助区分范围的枚举,不再允许在没有显式强制转换的情况下在下一条语句 account_num = Suit::Hearts; 中尝试转换。

联合

union 是用户定义的类型,其中所有成员都共享同一个内存位置。 这意味着在任何给定时间,联合都不能包含来自其成员列表的多个对象。 这还意味着无论联合具有多少成员,它始终仅使用足以存储最大成员的内存。
具有大量对象和/或内存有限时,联合可用于节省内存。 但是,需要格外小心才能正确使用它们,因为由你负责确保可始终访问写入的最后一个成员。 如果任何成员类型具有不常用构造函数,则必须编写附加代码来显式构造和销毁该成员。 使用联合之前,应考虑是否可以使用基类和派生类来更好地表示尝试解决的问题。

union [name] { member-list };

参数
name
为联合提供的类型名称。
member-list
联合可以包含的成员。 请参阅“备注”。
备注
声明联合
利用 union 关键字开始联合的声明,并用大括号包含成员列表:

// declaring_a_union.cpp
union RecordType  // Declare a simple union type
{
  char  ch;
  int  i;
  long  l;
  float f;
  double d;
  int *int_ptr;
};
int main()
{
  RecordType t;
  t.i = 5; // t holds an int
  t.f = 7.25 // t now holds a float
}

使用联合
在前面的示例中,任何访问联合的代码都需要了解保存数据的成员。 此问题最常见的解决方案是将联合以及其他枚举成员(指示当前存储在联合中的数据的类型)放入一个结构中。 这称为可区分的联合,下面的示例演示了基本模式。

#include "stdafx.h"
#include <queue>

using namespace std;

enum class WeatherDataType
{
  Temperature, Wind
};

struct TempData
{
  int StationId;
  time_t time;
  double current;
  double max;
  double min;
};

struct WindData
{
  int StationId;
  time_t time;
  int speed;
  short direction;
};

struct Input
{
  WeatherDataType type;
  union
  {
    TempData temp;
    WindData wind;
  };
};

// Functions that are specific to data types
void Process_Temp(TempData t) {}
void Process_Wind(WindData w) {}

// Container for all the data records
queue<Input> inputs;
void Initialize();

int main(int argc, char* argv[])
{
  Initialize();
  while (!inputs.empty())
  {
    Input i = inputs.front();
    switch (i.type)
    {
    case WeatherDataType::Temperature:
      Process_Temp(i.temp);
      break;
    case WeatherDataType::Wind:
      Process_Wind(i.wind);
      break;
    default:
      break;
    }
    inputs.pop();

  }
  return 0;
}

void Initialize()
{
  Input first, second;
  first.type = WeatherDataType::Temperature;
  first.temp = { 101, 1418855664, 91.8, 108.5, 67.2 };
  inputs.push(first);

  second.type = WeatherDataType::Wind;
  second.wind = { 204,1418859354, 14, 27 };
  inputs.push(second);
}

在前面的示例中,请注意 Input 结构中的联合没有名称。 这是匿名联合,可以访问其成员,如同它们是结构的直接成员一样。 有关匿名联合的详细信息,请参阅下面一节。
当然,上面的示例演示的问题也可以通过以下方法解决:使用派生自公共基类的类,并基于容器中每个对象的运行时类型对代码进行分支。 这可以生成更易于维护和理解的代码,但是也可能比使用联合更慢。 此外,通过联合可以存储完全不相关的类型,并动态更改存储的值的类型,而无需更改联合变量本身的类型。 因此可以创建其元素存储不同类型的不同值的 MyUnionType 异类数组。
请注意,可能会很容易误用前面示例中的 Input 结构。 完全由用户负责正确使用鉴别器来访问保存数据的成员。 你可以通过使联合成为专用并提供特殊访问函数(如下一个示例所示)来防止误用。
无限制的联合 (C++11)
在 C++03 及更低版本中,联合可以包含具有类类型的非静态数据成员,只要该类型没有用户提供的构造函数、析构函数或赋值运算符即可。 在 C++11 中,消除了这些限制。 如果在联合中包含这样一个成员,则编译器会自动将不是用户提供的任何特殊成员函数标记为已删除。 如果联合是类或结构中的匿名联合,则类或结构的不是用户提供的任何特殊成员函数都会标记为已删除。 下面的示例演示如何处理联合的某个成员具有需要此特殊处理的成员的情况:

// for MyVariant
#include <crtdbg.h>
#include <new>
#include <utility>

// for sample objects and output
#include <string>
#include <vector>
#include <iostream>

using namespace std;

struct A
{
  A() = default;
  A(int i, const string& str) : num(i), name(str) {}

  int num;
  string name;
  //...
};

struct B
{
  B() = default;
  B(int i, const string& str) : num(i), name(str) {}

  int num;
  string name;
  vector<int> vec;
  // ...
};

enum class Kind { None, A, B, Integer };

#pragma warning (push)
#pragma warning(disable:4624)
class MyVariant
{
public:
  MyVariant()
    : kind_(Kind::None)
  {
  }

  MyVariant(Kind kind)
    : kind_(kind)
  {
    switch (kind_)
    {
    case Kind::None:
      break;
    case Kind::A:
      new (&a_) A();
      break;
    case Kind::B:
      new (&b_) B();
      break;
    case Kind::Integer:
      i_ = 0;
      break;
    default:
      _ASSERT(false);
      break;
    }
  }

  ~MyVariant()
  {
    switch (kind_)
    {
    case Kind::None:
      break;
    case Kind::A:
      a_.~A();
      break;
    case Kind::B:
      b_.~B();
      break;
    case Kind::Integer:
      break;
    default:
      _ASSERT(false);
      break;
    }
    kind_ = Kind::None;
  }

  MyVariant(const MyVariant& other)
    : kind_(other.kind_)
  {
    switch (kind_)
    {
    case Kind::None:
      break;
    case Kind::A:
      new (&a_) A(other.a_);
      break;
    case Kind::B:
      new (&b_) B(other.b_);
      break;
    case Kind::Integer:
      i_ = other.i_;
      break;
    default:
      _ASSERT(false);
      break;
    }
  }

  MyVariant(MyVariant&& other)
    : kind_(other.kind_)
  {
    switch (kind_)
    {
    case Kind::None:
      break;
    case Kind::A:
      new (&a_) A(move(other.a_));
      break;
    case Kind::B:
      new (&b_) B(move(other.b_));
      break;
    case Kind::Integer:
      i_ = other.i_;
      break;
    default:
      _ASSERT(false);
      break;
    }
    other.kind_ = Kind::None;
  }

  MyVariant& operator=(const MyVariant& other)
  {
    if (&other != this)
    {
      switch (other.kind_)
      {
      case Kind::None:
        this->~MyVariant();
        break;
      case Kind::A:
        *this = other.a_;
        break;
      case Kind::B:
        *this = other.b_;
        break;
      case Kind::Integer:
        *this = other.i_;
        break;
      default:
        _ASSERT(false);
        break;
      }
    }
    return *this;
  }

  MyVariant& operator=(MyVariant&& other)
  {
    _ASSERT(this != &other);
    switch (other.kind_)
    {
    case Kind::None:
      this->~MyVariant();
      break;
    case Kind::A:
      *this = move(other.a_);
      break;
    case Kind::B:
      *this = move(other.b_);
      break;
    case Kind::Integer:
      *this = other.i_;
      break;
    default:
      _ASSERT(false);
      break;
    }
    other.kind_ = Kind::None;
    return *this;
  }

  MyVariant(const A& a)
    : kind_(Kind::A), a_(a)
  {
  }

  MyVariant(A&& a)
    : kind_(Kind::A), a_(move(a))
  {
  }

  MyVariant& operator=(const A& a)
  {
    if (kind_ != Kind::A)
    {
      this->~MyVariant();
      new (this) MyVariant(a);
    }
    else
    {
      a_ = a;
    }
    return *this;
  }

  MyVariant& operator=(A&& a)
  {
    if (kind_ != Kind::A)
    {
      this->~MyVariant();
      new (this) MyVariant(move(a));
    }
    else
    {
      a_ = move(a);
    }
    return *this;
  }

  MyVariant(const B& b)
    : kind_(Kind::B), b_(b)
  {
  }

  MyVariant(B&& b)
    : kind_(Kind::B), b_(move(b))
  {
  }

  MyVariant& operator=(const B& b)
  {
    if (kind_ != Kind::B)
    {
      this->~MyVariant();
      new (this) MyVariant(b);
    }
    else
    {
      b_ = b;
    }
    return *this;
  }

  MyVariant& operator=(B&& b)
  {
    if (kind_ != Kind::B)
    {
      this->~MyVariant();
      new (this) MyVariant(move(b));
    }
    else
    {
      b_ = move(b);
    }
    return *this;
  }

  MyVariant(int i)
    : kind_(Kind::Integer), i_(i)
  {
  }

  MyVariant& operator=(int i)
  {
    if (kind_ != Kind::Integer)
    {
      this->~MyVariant();
      new (this) MyVariant(i);
    }
    else
    {
      i_ = i;
    }
    return *this;
  }

  Kind GetKind() const
  {
    return kind_;
  }

  A& GetA()
  {
    _ASSERT(kind_ == Kind::A);
    return a_;
  }

  const A& GetA() const
  {
    _ASSERT(kind_ == Kind::A);
    return a_;
  }

  B& GetB()
  {
    _ASSERT(kind_ == Kind::B);
    return b_;
  }

  const B& GetB() const
  {
    _ASSERT(kind_ == Kind::B);
    return b_;
  }

  int& GetInteger()
  {
    _ASSERT(kind_ == Kind::Integer);
    return i_;
  }

  const int& GetInteger() const
  {
    _ASSERT(kind_ == Kind::Integer);
    return i_;
  }

private:
  Kind kind_;
  union
  {
    A a_;
    B b_;
    int i_;
  };
};
#pragma warning (pop)

int main()
{
  A a(1, "Hello from A");
  B b(2, "Hello from B");

  MyVariant mv_1 = a;

  cout << "mv_1 = a: " << mv_1.GetA().name << endl;
  mv_1 = b;
  cout << "mv_1 = b: " << mv_1.GetB().name << endl;
  mv_1 = A(3, "hello again from A");
  cout << R"aaa(mv_1 = A(3, "hello again from A"): )aaa" << mv_1.GetA().name << endl;
  mv_1 = 42;
  cout << "mv_1 = 42: " << mv_1.GetInteger() << endl;

  b.vec = { 10,20,30,40,50 };

  mv_1 = move(b);
  cout << "After move, mv_1 = b: vec.size = " << mv_1.GetB().vec.size() << endl;

  cout << endl << "Press a letter" << endl;
  char c;
  cin >> c;
}
#include <queue>
#include <iostream>
using namespace std;

enum class WeatherDataType
{
  Temperature, Wind
};

struct TempData
{
  TempData() : StationId(""), time(0), current(0), maxTemp(0), minTemp(0) {}
  TempData(string id, time_t t, double cur, double max, double min)
    : StationId(id), time(t), current(cur), maxTemp(max), minTemp(0) {}
  string StationId;
  time_t time = 0;
  double current;
  double maxTemp;
  double minTemp;
};

struct WindData
{
  int StationId;
  time_t time;
  int speed;
  short direction;
};

struct Input
{
  Input() {}
  Input(const Input&) {}

  ~Input()
  {
    if (type == WeatherDataType::Temperature)
    {
      temp.StationId.~string();
    }
  }

  WeatherDataType type;
  void SetTemp(const TempData& td)
  {
    type = WeatherDataType::Temperature;

    // must use placement new because of string member!
    new(&temp) TempData(td);
  }

  TempData GetTemp()
  {
    if (type == WeatherDataType::Temperature)
      return temp;
    else
      throw logic_error("Can't return TempData when Input holds a WindData");
  }
  void SetWind(WindData wd)
  {
    // Explicitly delete struct member that has a
    // non-trivial constructor
    if (type == WeatherDataType::Temperature)
    {
      temp.StationId.~string();
    }
    wind = wd; //placement new not required.
  }
  WindData GetWind()
  {
    if (type == WeatherDataType::Wind)
    {
      return wind;
    }
    else
      throw logic_error("Can't return WindData when Input holds a TempData");
  }

private:

  union
  {
    TempData temp;
    WindData wind;
  };
};

联合不能存储引用。 联合不支持继承,因此联合本身不能用作基类、继承自另一个类或具有虚函数。
初始化联合
可以通过指定包含在括号中的表达式来在相同语句中声明并初始化联合。 计算该表达式并将其分配给联合的第一个字段。

#include <iostream>
using namespace std;

union NumericType
{
  short    iValue;
  long    lValue;
  double   dValue;
};

int main()
{
  union NumericType Values = { 10 };  // iValue = 10
  cout << Values.iValue << endl;
  Values.dValue = 3.1416;
  cout << Values.dValue) << endl;
}
/* Output:
 10
 3.141600
*/

NumericType 联合排列在内存中(概念性的),如下图所示。

匿名联合
匿名联合是声明的没有 class-name 或 declarator-list 的联合。
union { member-list }
匿名联合中声明的名称可直接使用,就像非成员变量一样。 因此,匿名联合中声明的名称必须在周边范围中是唯一的。
除了联合成员数据中列出的限制之外,匿名联合还受其他限制:
如果在文件或命名空间范围内声明联合,则还必须将它们声明为“静态的”。
它们可以只具有公共成员;匿名联合中的私有成员和受保护的成员会生成错误。
它们不能具有函数成员。

(0)

相关推荐

  • C++第11版本中的一些强大的新特性小结

    Auto Type Deduction 自动类型推导 auto 关键字让用户得以使用 C++ 内置的类型推导特性. std::string something = somethingthatreturnsastring.getString(); auto something = somethingthatreturnsastring.getString(); Auto 关键字会对上述自变量(something)进行自动推导,得出其应该是 string 类型的结论,并在 auto 出现的地方用正确

  • C++11的新特性简单汇总介绍 (二)

    1. 范围for语句 C++11 引入了一种更为简单的for语句,这种for语句可以很方便的遍历容器或其他序列的所有元素 vector<int> vec = {1,2,3,4,5,6}; for(int x: vec) { cout<<x<<endl; } 2. 尾置返回类型 要想引入尾置类型,我们还得从复杂的类型声明说起.如果我们需要定义一个含有10个int元素的数组,一般是这样的: int arr[10] = {0}; 如果要定义指向这个数组的指针呢: 复制代码 代

  • C++ 11实现检查是否存在特定的成员函数

    问题提出 最近工作中遇到这样一个需求:实现一个ToString函数将类型T转换到字符串,如果类型T中含有同名方法ToString则直接调用. 这样一个ToString实现可以使用std::enable_if来做到,但是这里的难点在于如何判断类型T中存在这样一个ToString方法,以便可以放入enable_if中做SFINAE. 检查类中是否存在特定成员 相同的问题在知乎上有人提出过,@孙明琦的答案提供了一个用于检测特定检测子U在类型T下是否有效的检测器is_detected_v.其中用到了一个

  • c++11新增的便利算法实例分析

    C++是一门应用非常广泛的程序设计语言,而c++11则新增加了一些便利的算法,这些新增的算法使我们的代码写起来更简洁方便,本文列举一些常用的新增算法,算是做个总结分析,更多的新增算法读者可以参考:http://en.cppreference.com/w/cpp/algorithm. 算法库新增了三个用于判断的算法all_of.any_of和none_of,定义如下: template< class InputIt, class UnaryPredicate > bool all_of( Inp

  • C++11的新特性简单汇总介绍 (一)

    什么是C++11 C++11是曾经被叫做C++0x,是对目前C++语言的扩展和修正,C++11不仅包含核心语言的新机能,而且扩展了C++的标准程序库(STL),并入了大部分的C++ Technical Report 1(TR1)程序库(数学的特殊函数除外). C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto.decltype,和模板的大量改进. 1. 概述 最近在看C++ Primer5 刚好看到一半,总结一下C++11里面确实加了很多新东西,如果没有任何了解,别说自己

  • 结合C++11新特性来学习C++中lambda表达式的用法

    在 C++ 11 中,lambda 表达式(通常称为 "lambda")是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象的简便方法. Lambda 通常用于封装传递给算法或异步方法的少量代码行. 本文定义了 lambda 是什么,将 lambda 与其他编程技术进行比较,描述其优点,并提供一个基本示例. Lambda 表达式的各部分 ISO C++ 标准展示了作为第三个参数传递给 std::sort() 函数的简单 lambda: #include <algorit

  • 结合C++11的新特性来解析C++中的枚举与联合

    枚举 枚举是用户定义的类型,其中包含一组称为枚举器的命名的整型常数. 语法 // unscoped enum: enum [identifier] [: type] {enum-list}; // scoped enum: enum [class|struct] [identifier] [: type] {enum-list}; // Forward declaration of enumerations (C++11): enum A : int; // non-scoped enum mu

  • iOS StoreKit 2 新特性盘点解析

    目录 一.背景 二.物料 三.StoreKit 1 存在的问题 四.StoreKit v2 新特性 五.StoreKit 2 API 5.1 只支持 Swift 开发 原始获取商品方式 新获取商品方式 5.2 新 API 5.2.1 Product 5.2.2 Transaction History 5.2.3 Subscription status 5.2.4 show manager subscriptions 5.2.5 request refund API 小结: 六.Server to

  • Java8中新特性Optional、接口中默认方法和静态方法详解

    前言 毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库.工具和JVM等方面的十多个新特性. Java 8是Java的一个重大版本,有人认为,虽然这些新特性领Java开发人员十分期待,但同时也需要花不少精力去学习.下面本文就给大家详细介绍了Java8中新特性Optional.接口中默认方法和静态方法的相关内容,话不多说了,来一起看看详细的介绍吧. Optional Optional 类(java.util.Optional) 是一个

  • java8新特性之stream流中reduce()求和知识总结

    1.stream().reduce()单字段求和 (1)普通数字求和 public static void test2(){ List<Integer> list= Arrays.asList(new Integer[]{1,2,3,4,5,6,7,8,9}); Integer sum=list.stream().reduce((x,y)->x+y).get(); System.out.println(sum); } 2.BigDecimal求和 public static void m

  • Swift 4最全的新特性详细解析(推荐)

    引言 Swift,苹果于2014年WWDC(苹果开发者大会)发布的新开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序.Swift吸收了众多现代编程语言的优点,尽力的提供简洁的编程语言和强大的功能. WWDC 2017 给大家带来了很多惊喜.Swift 4 也伴随着 Xcode 9 测试版来到了我们的面前,很多强大的新特性非常值得我们期待在正式项目中去使用它.因为 Swift 4 是开源的,如果你关注 swift-evolution 这个项目的

  • 关于iOS 11的一些新特性适配实践总结

    前言 iOS 11 已经发布了一段时间了,随手记团队也早早的完成了适配.在这里,我们做了点总结,与大家一起分享一下关于 iOS 11 一些新特性的适配. UIView & UIViewController Layout Margins iOS 11 中,官方提供了一种新的布局方法--通过 layout margins 进行布局.官方文档 Positioning Content Within Layout Margins 称,使用这种布局可以保证各个 content 之间不会相互覆盖. 总的来说,

  • C++11新特性“=default”,“=delete”的使用

    1. =default 和=delete 概述 任何事物的出现都必然有着其出现的理由,伴随着每一个新的概念产生都会带来一系列的便利和价值.C++在不断的演变与发展,与此同时,伴随着许多新的特性和功能产生.=default.=delete 是C++11的新特性,分别为:显式缺省(告知编译器生成函数默认的缺省版本)和显式删除(告知编译器不生成函数默认的缺省版本).C++11中引进这两种新特性的目的是为了增强对"类默认函数的控制",从而让程序员更加精准地去控制默认版本的函数.其具体的功能和使

随机推荐