65.9K
CodeProject 正在变化。 阅读更多。
Home

C++开发人员会喜欢VS 14 CTP的7个理由

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (52投票s)

2014年7月24日

CPOL

6分钟阅读

viewsIcon

75769

本文介绍了VS 14 CTP 2中针对C++开发的七个语言和IDE特性。

引言

这绝不是 VS 14 CTP 中 C++ 功能的详尽列表,它也没有试图这样做。这仅仅是我认为从实际角度对 C++ 开发者有吸引力的一些 C++ 语言和 IDE 功能的精选。这些是开发者可以立即开始在代码中使用的功能。至于“7 个理由”这个标题,我采用了新闻报道的标题风格,因为我想不出更好的了。

用户定义字面量

用户定义字面量是 C++ 11 的一项功能,已在 VS 14 CTP 中实现。一些标准头文件已经更新,以定义用户定义字面量。例如,`` 有一个用于 **string** 字面量的 **s** 后缀。所以你现在可以这样做,这两行代码是等价的。

auto s1 = "hello"s;
auto s2 = string("hello");

(CTP 版本中)`` 中的定义如下所示:

inline string operator "" s(const char *_Str, size_t _Len)
{ // construct literal from [_Str, _Str + _Len)
  return (string(_Str, _Len));
}

这是一个常见的例子,用于演示如何在代码中使用用户定义字面量。考虑以下 **Weight** 类。

struct Weight
{
  WeightUnitType Unit;

  double Value;

  double Lb;

  Weight(WeightUnitType unitType, double value)
  {
    Value = value;
    Unit = unitType;

    if (Unit == WeightUnitType::Lb)
    {
      Lb = value;
    }
    else
    {
      Lb = 2.2 * value;
    }
  }
};

现在,你可以为这个类定义 **_kg** 和 **_lb** 字面量运算符。

Weight operator "" _kg(long double value)
{ 
  return (Weight(WeightUnitType::Kg, static_cast<double>(value)));
}

Weight operator "" _lb(long double value)
{
  return (Weight(WeightUnitType::Lb, static_cast<double>(value)));
}

下面是如何在代码中使用它们。

auto w1 = 10.0_kg;
auto w2 = 22.0_lb;

cout << (w1.Lb == w2.Lb) << endl; // outputs 1 (true)

请注意,你的字面量后缀必须以 _ 开头。否则,你会收到一个错误。

literal suffix identifiers that do not start 
with an underscore are reserved

我猜 `` 不需要下划线,因为它被允许作为一个特殊情况。

auto 返回类型

VC++ 团队没有先实现所有 C++ 11 功能然后再以 C++ 14 为目标,而是采取了一种并行实现两者的方法。这将使他们能够比不那么受欢迎的 C++ 11 功能更早地实现流行的 C++ 14 功能。无论如何,在某个时候,他们都会发布一个完全支持 C++ 11 和 C++ 14 的版本。一个非常普遍要求的 C++ 14 功能是 **auto/decltype(auto)** 返回类型,CTP 支持两者。这是一个例子,使用它节省了一些输入,代码看起来也更简洁。

auto Foo()
{
  map<int, vector<pair<int, string>>> vec;
  return vec;
}

当然,这是主观的,有些人可能会觉得这里的 **auto** 令人困惑。它在模板中更有用,其中返回类型通常需要 **decltype**。

template<class T1, class T2> auto Foo(T1 a, T2 b)
{
  return a + b;
}

你可以将它与类方法一起使用,甚至进行前向声明。

class C
{
public: 
  auto Foo();
};

auto C::Foo()
{
  return 10;
}

也支持多个返回,这同样适用于你的 lambda 表达式。

auto Bar()
{
  C c;
  if (c.Foo() < 10)
  {
    return 3.3;
  }

  return 1.7;
}

使用 **decltype(auto)** 可以为你提供更大的灵活性。考虑这段代码。

int F1()
{
  return 5;
}

int  i = 5;
int& F2()
{
  return i;
}

现在,如果你这样调用它们:

  auto f1 = F1(); // int - correct
  auto f2 = F2(); // int - inaccurate, lost original type
  decltype(auto) f3 = F1(); // int - correct
  decltype(auto) f4 = F2(); // int& - correct

对于模板也是如此,你可以省去 **decltype**。

struct T
{
  int& Foo(int& i)
  {
    return i;
  }

  double Foo(double& d)
  {
    return d;
  }

  template<class T> decltype(auto) NewWay(T& t)
  {
    return Foo(t);
  }

  template<class T> auto OldWay(T& t) -> decltype(Foo(t))
  {
    return Foo(t);
  }
};

这是一个简单的例子,但库编写者会欣赏它,因为它极大地简化了他们的代码,并且在阅读他人代码时使其更容易理解。

继承构造函数

继承构造函数是 C++ 11 的一项功能,已在此 CTP 中实现。它扩展了 **using** 声明,允许派生类指示它需要继承基类的构造函数。这是一个基本示例。

struct Base
{
  Base(int){}
};

struct Derived : Base
{
  using Base::Base;
};

void Foo()
{
  Derived d1(10); // uses inherited Derived(int)
  Derived d2; // fails to compile, as Base has no default ctor
}

(在此 CTP 版本中)你会收到这个错误消息。

error C2280: 'Derived::Derived(void)': attempting to
reference a deleted function

如果 **Base** 有一个默认构造函数,或者现有的 **int** 构造函数有一个默认参数值,那么它本可以编译通过。

struct Base
{
  Base(int = 1, int = 2){}
  Base(string){}
};

struct Derived : Base
{
  using Base::Base;
};

void Foo()
{
  Derived d1;
  Derived d2(10);
  Derived d3(10,10);
  string s;
  Derived d4(s);
}

所有这些实例化都可以成功编译。如果你有多个基类,你可以指定多个 using 声明。

struct Base1
{
  Base1(int){}
};

struct Base2
{
  Base2(int, int){}
};

struct Derived : Base1, Base2
{
  using Base1::Base1;
  using Base2::Base2;
};

void Foo()
{
  Derived d1(1), d2(1, 1);
}

使用多个基类时,你需要确保没有构造函数冲突。示例:

struct Base1
{
  Base1(){}
  Base1(int){}
};

struct Base2
{
  Base2(){}
  Base2(int){}
  Base2(int, int){}
};

struct Derived : Base1, Base2
{
  using Base1::Base1;
  using Base2::Base2;
};

void Foo()
{
  Derived d1(1), d2(1, 1);
}

这不会编译。

error C3882: 'Base2::Base2': constructor has already
been inherited from 'Base1'

修复方法是在 **Derived** 中显式声明该构造函数。

struct Derived : Base1, Base2
{
  using Base1::Base1;
  using Base2::Base2;

  Derived(int){}
};

继承构造函数也适用于模板。

template<class T> struct Derived : T
{
  using T::T;
};

struct Base1
{
  Base1(int){}
};

struct Base2
{
  Base2(int, int){}
};

void Foo()
{
  Derived<Base1> d1(10);
  Derived<Base2> d2(10, 11);
}

这项功能将通过编译器为你生成显式的派生构造函数来节省你输入这些构造函数所需的时间和精力(使其不易出错)。

扩展 sizeof

C++ 11 提出了一项功能,可以将 `sizeof` 应用于非静态数据成员,而无需临时对象。CTP 实现​​了该功能。考虑以下结构。

struct S
{
  int data;
  char data2;
};

现在,在 Visual Studio 2013 中,以下代码将无法编译。

cout << sizeof(S::data) << endl;

你会收到这个错误消息。

Error 1 error C2070: 'unknown': illegal sizeof operand

你instead需要这样做。

S s;
cout << sizeof(s.data) << endl;
cout << sizeof(s.data2) << endl;

将其与 CTP 中支持的以下代码进行比较:

cout << sizeof(S::data) << endl;
cout << sizeof(S::data2) << endl;

这是一个简单功能,但它将帮助你编写更简洁的代码。

IDE - 创建声明/定义

CTP 2 为你的 C++ 项目提供了一个基本稳定的“创建声明/定义”重构工具。它允许你自动生成成员函数的定义或声明。例如,如果你有一个名为 **Employee** 的类,在 `Employee.h` 中声明,在 `Employee.cpp` 中定义,你可以在 .h 文件中输入一个函数声明,然后在 cpp 文件中自动生成其主体。

图 1:从定义创建声明

你将得到一个空的定义。

int Employee::Foo(int x, int y, double d)
{
    return 0;
}

你也可以反过来做。假设你想添加一个不带 **double** 参数的重载。只需复制粘贴该定义,删除 double 参数,然后使用重构选项。

图 2:从声明创建定义

它会为你生成这个。

int Foo(int x, int y);

非常有用。话虽如此,我希望它能做这样的事情。如果我有这样的代码。

Employee e;
e.Bar();

如果你右键单击 **Bar()** 并选择此重构选项,你将收到一条消息:“选定的文本不包含任何函数签名。” 如果能这样就好了。C# 至少已经有两年了。

IDE - 移动定义位置

CTP 添加了一个重构选项,可以将函数定义从头文件移动到 cpp 文件,反之亦然。只需右键单击定义,选择 **Refactor/Move Definition Location**,即可完成。

// h file
class Employee
{
public:
  Employee();

  int Foo(int x, int y);
};

// cpp file
Employee::Employee()
{
}

int Employee::Foo(int x, int y)
{
  return 0;
}

图 3:在 h/cpp 文件之间移动定义位置

现在你的代码看起来像这样。

class Employee
{
public:
  Employee();

  int Foo(int x, int y)
  {
    return 0;
  }
};

你也可以反过来做。这个 CTP 中似乎有一个 bug - 当你从 h 文件移动到 cpp 文件时,它不会添加类名前缀,所以你会得到这个。

int Foo(int x, int y)
{
  return 0;
}

我猜这个 bug 会在 RTM 版本中修复。虽然这是一个看似微小的功能,但它非常方便,一旦你习惯了它,当你(在使用旧版本或不同的 IDE 时)没有它时,你就会开始想念它。

IDE - 实现纯虚函数

此重构选项为所有纯虚函数实现存根函数,这些函数在一个或所有基类中。这是一个例子。

class Person
{
public:

  virtual std::string GetName() = 0;
  virtual void SetName(std::string) = 0;
};

class DbEntity
{
public:
  virtual int GetId() = 0;
  virtual void SetId(int) = 0;
};

class Employee : Person, DbEntity
{
public:
  Employee();
};

如果你右键单击特定的基类,你将获得仅为该类实现纯虚函数的选项。如果你右键单击派生类名称,你将获得实现所有基类的所有纯虚函数的选项。

图 4:实现所有纯虚函数的重构工具

这就是你最终会得到的结果(在头文件中)。

class Employee : Person, DbEntity
{
public:
  Employee();
  // Inherited via Person

  virtual std::string GetName() override;

  virtual void SetName(std::string) override;

  // Inherited via DbEntity

  virtual int GetId() override;

  virtual void SetId(int) override;

};

cpp 文件中将生成相应的定义(空的)。

一如既往,欢迎提供反馈,包括批评性建议。谢谢。

链接

历史

  • 2014 年 7 月 25 日:添加了 CTP 链接
  • 2014 年 7 月 24 日:首次发布
© . All rights reserved.