C + + 的隐藏特性?

当涉及到“隐藏特性”这一系列问题时,没有 C + + 的爱吗?我觉得我应该说出来。C + + 的一些隐藏特性是什么?

92469 次浏览

我不知道隐藏,但有一些 interesting “把戏”,可能不是很明显,只是从阅读规格。

C + + 是一个标准,不应该有任何隐藏的功能..。

C + + 是一种多范式的语言,你可以把你的最后一分钱押在那里有隐藏的功能。众多例子中的一个: 模板超编程。标准委员会中没有人打算在编译时执行图灵完成子语言。

我发现这个博客是一个关于 C + + : C + + 真理奥秘的惊人资源。

虽然没有隐藏的特性,但是 C + + 语言非常强大,甚至连标准开发人员都无法想象 C + + 可以用来做什么。

事实上,从足够简单的语言结构,你可以写一些非常强大的东西。 很多这样的例子可以在 www.boost.org 上找到(其中包括 http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html)。

要了解如何简单的语言构造可以结合到一些强大的东西,这是很好的阅读 “ C + + 模板: 完整指南”作者: David Vandevoorde,Nicolai M. Josuttis和真正神奇的书 "Modern C++ Design ... " by Andrei Alexandrescu

最后,学习 C + + 是很困难的,你应该尝试去填补它;)

有很多“未定义行为”。你可以学习如何避免他们阅读好书和阅读标准。

在 C + + 中有大量“棘手”的结构。 它们从使用虚继承的“简单”的 密封/期末课程实现转变而来。 并获得相当“复杂”的元编程结构,如 Boost 的 MPL(tutorial)。给自己制造麻烦的可能性是无穷无尽的,但是如果经过检查(比如经验丰富的程序员) ,就可以在可维护性和性能方面提供一些最好的灵活性。

众多例子中的一个: 模板 元编程。没有人在 standards committee intended there to be a Turing-complete sublanguage that 在编译时执行。

模板超编程几乎不是一个隐藏的特征。甚至还在图书馆里。参见 MPL。但是,如果“几乎隐藏”是足够好的,然后看看 boost libraries。它包含许多好东西,如果没有一个强大的图书馆的支持,这些好东西是不容易获得的。

一个例子是 加油 Lambda库,它很有趣,因为 C + + 在当前标准中没有 lambda 函数。

另一个例子是 洛基,它“广泛使用了 C + + 模板超编程,并实现了几个常用的工具: 类型表、函数、单例、智能指针、对象工厂、访问者和多方法。”[ 维基百科]

很少有人知道临时人员的生命周期必须遵循引用。或者至少是我最喜欢的一部分 C + + 知识,大多数人都不知道。

const MyClass& x = MyClass(); // temporary exists as long as x is in scope

数组运算符是关联的。

A [8]是 * (A + 8)的同义词。因为加法是结合的,所以可以重写为 * (8 + A) ,这是... ... 8[ A ]的同义词

你没说有用... : -)

大多数 c + + 开发人员忽视了模板超编程的力量。看看 Loki Libary。它实现了一些高级的工具,如类型表、函数、单例、智能指针、对象工厂、访问者和多方法,广泛使用模板超编程(来自 维基百科)。 在大多数情况下,您可以将这些看作是“隐藏的”c + + 特性。

我同意那里的大多数帖子: C + + 是一种多范式的语言,所以你将发现的“隐藏”特性(除了“未定义的行为”,你应该不惜一切代价避免)是对设施的巧妙使用。

这些工具中的大多数不是语言的内置特性,而是基于库的特性。

最重要的是 拉尔,它常常被来自 C 世界的 C + + 开发人员忽视多年。Operator overloading经常被误解,它支持类似数组的行为(下标运算符)、类似指针的操作(智能指针)和类似内建的操作(矩阵乘法)。

使用 例外通常是困难的,但是通过一些工作,可以通过 异常安全规范生成真正健壮的代码(包括不会失败的代码,或者具有类似于提交的特性的代码,这些特性将会成功,或者恢复到原来的状态)。

C + + 最著名的“隐藏”特性是 template metaprogramming,因为它允许在编译时而不是运行时部分(或全部)执行程序。但是这很困难,在尝试之前必须对模板有充分的了解。

其他人则利用多重范式在 C + + 的祖先,即 C 语言之外产生“编程方式”。

通过使用 函子,您可以模拟函数,使用附加的类型安全和有状态。使用 指挥官模式,可以延迟代码执行。大多数其他的 设计模式可以在 C + + 中轻松有效地实现,从而产生不应该在“官方 C + + 范例”列表中的替代编码风格。

通过使用 模板,您可以生成适用于大多数类型的代码,包括您最初认为不适用的代码。您还可以增加类型安全性(比如自动类型安全的 malloc/realloc/free)。C + + 对象特性真的很强大(因此,如果不小心使用就会很危险) ,但即使是 动态多态性动态多态性也有它在 C + + 中的静态版本: CRTP

我发现大多数来自 Scott Meyers 的“ 有效的 C + + ”类型的书或者 Herb Sutter 的“ 卓越的 C + + ”类型的书都很容易阅读,并且在 C + + 的已知和不太为人所知的特性方面有着相当丰富的信息。

我最喜欢的是一个能让任何 Java 程序员毛骨悚然的东西: 在 C + + 中,the most object-oriented way to add a feature to an object is through a non-member non-friend function, instead of a member-function(即类方法) ,因为:

  • 在 C + + 中,类的接口是同一命名空间中的成员函数和非成员函数

  • 非好友非成员函数没有访问内部类的特权。因此,在非成员非好友函数上使用成员函数将削弱类的封装。

即使是经验丰富的开发人员也会对此感到惊讶。

(Source: Among others, Herb Sutter's online Guru of the Week #84: http://www.gotw.ca/gotw/084.htm )

哦,我可以列出一张讨厌宠物的清单:

  • 如果您打算以多态方式使用析构函数,那么它必须是虚函数
  • Sometimes members are initialized by default, sometimes they aren't
  • 本地类不能用作模板参数(使它们不太有用)
  • 异常说明符: 看起来很有用,但实际上不是
  • 函数重载隐藏具有不同签名的基类函数。
  • 没有有用的国际化标准(便携式标准宽字符集,有人吗? 我们要等到 C + + 0x)

往好的方面想

  • 隐藏特性: 函数 try 块。不幸的是,我还没找到它的用处。是的,我知道他们为什么添加它,但是你必须重新引入一个构造函数,这使得它毫无意义。
  • 值得仔细研究一下在容器修改后 STL 对迭代器有效性的保证,这可以让您制作一些稍微好一点的循环。
  • 提高-这几乎不是一个秘密,但它值得使用。
  • 返回值优化(不明显,但标准特别允许)
  • 函数又名函数对象又名操作符()。这被 STL 广泛使用。这其实不是什么秘密,而是运算符重载和模板的一个巧妙的副作用。

有一个语言特性我认为有些隐藏,因为我在学校的整个时间里从来没有听说过,那就是名称空间别名。直到我在升级文档中遇到它的例子,它才引起我的注意。当然,现在我了解了它,您可以在任何标准的 C + + 引用中找到它。

namespace fs = boost::filesystem;


fs::path myPath( strPath, fs::native );
  • 指向类方法的指针
  • “ typeename”关键字

You can put URIs into C++ source without error. For example:

void foo() {
http://stackoverflow.com/
int bar = 4;


...
}

指针算术。

它实际上是一个 C 特性,但是我注意到使用 C/C + + 的人很少真正意识到它的存在。我认为 C 语言的这一特性真正展示了其发明者的天才和远见。

长话短说,指针算法允许编译器将[ n ]作为 * (a + n)对任何类型的 a 执行。顺便说一句,由于“ +”是可交换的,所以[ n ]当然等价于 n [ a ]。

去掉前向声明:

struct global
{
void main()
{
a = 1;
b();
}
int a;
void b(){}
}
singleton;

使用? : 操作符编写 switch 语句:

string result =
a==0 ? "zero" :
a==1 ? "one" :
a==2 ? "two" :
0;

在一条线上做所有事情:

void a();
int b();
float c = (a(),b(),1.0f);

没有 memset 的结构体零点:

FStruct s = {0};

正常化/包装角度和时间值:

int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150

指定参考文献:

struct ref
{
int& r;
ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;

一个不常用的好特性是函数范围的 try-catch 块:

int Function()
try
{
// do something here
return 42;
}
catch(...)
{
return -1;
}

主要用法是将异常转换为其他异常类并重新抛出,或者在异常和基于返回的错误代码处理之间进行转换。

隐藏特征:

  1. 纯虚函数可以有实现。常见的例子是纯虚析构函数。
  2. 如果一个函数抛出一个异常,这个异常没有在它的异常规范中列出,但是这个函数在它的异常规范中有 std::bad_exception,这个异常会被转换成 std::bad_exception并自动抛出。这样你至少会知道一个 bad_exception被抛出。阅读更多 给你

  3. 函数 try 块

  4. 在类模板中消除类型定义歧义的模板关键字。如果成员模板专门化的名称出现在 .->::运算符之后,并且该名称具有显式限定的模板参数,则在成员模板名称前面加上关键字模板。阅读更多 给你

  5. 函数参数默认值可以在运行时更改。

  6. A[i]i[A]一样好

  7. 可以修改类的临时实例!可以对临时对象调用非常量成员函数。例如:

    struct Bar {
    void modify() {}
    }
    int main (void) {
    Bar().modify();   /* non-const function invoked on a temporary. */
    }
    

    Read more 给你.

  8. If two different types are present before and after the : in the ternary (?:) operator expression, then the resulting type of the expression is the one that is the most general of the two. For example:

    void foo (int) {}
    void foo (double) {}
    struct X {
    X (double d = 0.0) {}
    };
    void foo (X) {}
    
    
    int main(void) {
    int i = 1;
    foo(i ? 0 : 0.0); // calls foo(double)
    X x;
    foo(i ? 0.0 : x);  // calls foo(X)
    }
    

构造函数中的数组初始化。 例如,在一个类中,如果我们有一个 int数组:

class clName
{
clName();
int a[10];
};

We can initialize all elements in the array to its default (here all elements of array to zero) in the constructor as:

clName::clName() : a()
{
}

map::operator[] creates entry if key is missing and returns reference to default-constructed entry value. So you can write:

map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
s.assign(...);
}
cout << s;

我很惊讶有多少 C + + 程序员不知道这一点。

将函数或变量放在无名命名空间中不推荐使用 static将它们限制在文件范围内。

大多数 C + + 程序员都熟悉三元运算符:

x = (y < 0) ? 10 : 20;

However, they don't realize that it can be used as an lvalue:

(a == 0 ? a : b) = 1;

简称

if (a == 0)
a = 1;
else
b = 1;

小心使用: -)

将文件读入字符串向量:

 vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));

Istream _ iterator

指针算术。

C++ programmers prefer to avoid pointers because of the bugs that can be introduced.

The coolest C++ I've ever seen though? 模拟字面意思。

一个非常隐藏的特性是,您可以在 if 条件中定义变量,它的范围将仅覆盖 if 和 else 块:

if(int * p = getPointer()) {
// do something
}

一些宏使用它,例如提供一些“锁定”作用域,如下所示:

struct MutexLocker {
MutexLocker(Mutex&);
~MutexLocker();
operator bool() const { return false; }
private:
Mutex &m;
};


#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else


void someCriticalPath() {
locked(myLocker) { /* ... */ }
}

此外,BOOST _ FOREACH 在幕后使用它。为了完成这个任务,不仅可以使用 if,还可以使用 switch:

switch(int value = getIt()) {
// ...
}

在 while 循环中:

while(SomeThing t = getSomeThing()) {
// ...
}

但我不太确定这些是否都有用:)

来自 C + + 真理

Defining functions having identical signatures in the same scope, so this is legal:

template<class T> // (a) a base template
void f(T) {
std::cout << "f(T)\n";
}


template<>
void f<>(int*) { // (b) an explicit specialization
std::cout << "f(int *) specilization\n";
}


template<class T> // (c) another, overloads (a)
void f(T*) {
std::cout << "f(T *)\n";
}


template<>
void f<>(int*) { // (d) another identical explicit specialization
std::cout << "f(int *) another specilization\n";
}

如果操作符 delete ()除了 * void 之外还有 size 参数,这意味着它将是一个基类。使用 size 参数可以检查类型的大小,以便销毁正确的类型。Stephen Dewhurst告诉我们:

注意,我们还使用了一个 two-argument version of operator 删除而不是通常的删除 一个论点的版本,这个 two-argument version is another "usual" version of member operator 基类经常使用的删除 期望派生类继承的 它们的操作符删除实现。 第二个参数将包含 物体的大小 deleted—information that is often 有助于实现自定义内存 管理层。

任何编程语言中最有趣的语法之一。

其中三个属于一起,两个完全不同..。

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

除了第三个和第五个对象外,所有对象都在堆栈上定义一个 SomeType对象并初始化它(前两个对象使用的是 u,第四个对象使用的是缺省构造函数。第三种方法是声明一个不接受任何参数并返回 SomeType的函数。第五种方法类似地声明了一个函数,该函数按值接受一个名为 uSomeType类型的参数。

一件鲜为人知的事情是,工会也可以成为模板:

template<typename From, typename To>
union union_cast {
From from;
To   to;


union_cast(From from)
:from(from) { }


To getTo() const { return to; }
};

它们也可以有构造函数和成员函数,只是与继承无关(包括虚函数)。

在类模板中定义普通朋友函数需要特别注意:

template <typename T>
class Creator {
friend void appear() {  // a new function ::appear(), but it doesn't
…                   // exist until Creator is instantiated
}
};
Creator<void> miracle;  // ::appear() is created at this point
Creator<double> oops;   // ERROR: ::appear() is created a second time!

在本例中,两个不同的实例化创建了两个相同的定义ーー这直接违反了 ODR

因此,我们必须确保类模板的模板参数以该模板中定义的任何朋友函数的类型出现(除非我们想要防止在特定文件中多个类模板的实例化,但这是不太可能的)。让我们把这个应用到我们之前的例子的一个变体:

template <typename T>
class Creator {
friend void feed(Creator<T>*){  // every T generates a different
…                           // function ::feed()
}
};


Creator<void> one;     // generates ::feed(Creator<void>*)
Creator<double> two;   // generates ::feed(Creator<double>*)

免责声明: 我粘贴了 C + + 模板: 完整指南/Section 8.4中的这一部分

一个危险的秘密是

Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
f->~Fred();

我最喜欢的秘密我很少看到使用:

class A
{
};


struct B
{
A a;
operator A&() { return a; }
};


void func(A a) { }


int main()
{
A a, c;
B b;
a=c;
func(b); //yeah baby
a=b; //gotta love this
}
class Empty {};


namespace std {
// #1 specializing from std namespace is okay under certain circumstances
template<>
void swap<Empty>(Empty&, Empty&) {}
}


/* #2 The following function has no arguments.
There is no 'unknown argument list' as we do
in C.
*/
void my_function() {
cout << "whoa! an error\n"; // #3 using can be scoped, as it is in main below
// and this doesn't affect things outside of that scope
}


int main() {
using namespace std; /* #4 you can use using in function scopes */
cout << sizeof(Empty) << "\n"; /* #5 sizeof(Empty) is never 0 */
/* #6 falling off of main without an explicit return means "return 0;" */
}

间接转换成语 :

假设你正在设计一个 指针类。除此之外 overloading the operators * and ->, a 智能指针类通常定义一个 转换运算符到 bool:

template <class T>
class Ptr
{
public:
operator bool() const
{
return (rawptr ? true: false);
}
//..more stuff
private:
T * rawptr;
};

转换为 bool 允许客户端 在表达式中使用智能指针 需要 bool 操作数的:

Ptr<int> ptr(new int);
if(ptr ) //calls operator bool()
cout<<"int value is: "<<*ptr <<endl;
else
cout<<"empty"<<endl;

此外,隐式转换 有条件的 声明,例如:

if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
//we get here only of px isn't empty
}

唉,这个自动转换将打开 the gate to unwelcome surprises:

Ptr <int> p1;
Ptr <double> p2;


//surprise #1
cout<<"p1 + p2 = "<< p1+p2 <<endl;
//prints 0, 1, or 2, although there isn't an overloaded operator+()


Ptr <File> pf;
Ptr <Query> pq; // Query and File are unrelated


//surprise #2
if(pf==pq) //compares bool values, not pointers!

解决方案: 使用“间接转换”这个习惯用法,通过一个从指针到数据成员[ pMember ]到 bool 的转换,这样就只有一个隐式转换,这将防止前面提到的意外行为: pMember-> bool 而不是那个 bool-> 别的。

注意免费函数指针和成员函数指针初始化的区别:

成员职能:

struct S
{
void func(){};
};
int main(){
void (S::*pmf)()=&S::func;//  & is mandatory
}

免费功能:

void func(int){}
int main(){
void (*pf)(int)=func; // & is unnecessary it can be &func as well;
}

感谢这个冗余 & ,你可以添加流操作器-这是免费的功能-在链中没有它:

cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.

static cast模拟 重新演绎演员阵容:

int var;
string *str = reinterpret_cast<string*>(&var);

the above code is equivalent to following:

int var;
string *str = static_cast<string*>(static_cast<void*>(&var));

The class and struct class-keys are nearly identical. The main difference is that classes default to private access for members and bases, while structs default to public:

// this is completely valid C++:
class A;
struct A { virtual ~A() = 0; };
class B : public A { public: virtual ~B(); };


// means the exact same as:
struct A;
class A { public: virtual ~A() = 0; };
struct B : A { virtual ~B(); };


// you can't even tell the difference from other code whether 'struct'
// or 'class' was used for A and B

联合还可以具有成员和方法,并且默认为公共访问,类似于结构。

不仅可以在 for循环的 init 部分声明变量,还可以声明类和函数。

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}

允许不同类型的多个变量。

向模板添加 约束

基元类型具有构造函数。

int i(3);

工程。

在我看来,只有少数人知道未命名的名称空间:

namespace {
// Classes, functions, and objects here.
}

Unnamed namespaces behave as if they was replaced by:

namespace __unique_name__ { /* empty body */ }
using namespace __unique_name__;
namespace __unique_name__ {
// original namespace body
}

".. where all occurances of [this unique name] in a translation unit are replaced by the same identifier and this identifier differs from all other identifiers in the entire program." [C++03, 7.3.1.1/1]

你可以访问任何类的受保护的数据和函数成员,不需要未定义行为,只需要预期的语义。继续往下读。请阅读 缺陷报告

通常,C + + 禁止您访问类对象的非静态受保护成员,即使该类是您的基类

struct A {
protected:
int a;
};


struct B : A {
// error: can't access protected member
static int get(A &x) { return x.a; }
};


struct C : A { };

这是禁止的: 您和编译器都不知道引用实际指向的是什么。它可以是一个 C对象,在这种情况下,类 B没有关于其数据的业务和线索。只有当 x是对派生类的引用或从派生类派生的引用时,才授予这种访问权限。它可以允许任意一段代码读取任何受保护的成员,只需要组成一个“扔掉”类,读取成员,例如 std::stack:

void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
// error: stack<int>::c is protected
return s.c;
}
};


// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}

当然,如你所见,这会造成很大的伤害。但是现在,成员指针允许绕过这种保护!关键点是,成员指针的类型绑定到实际包含所述成员 -没有的类,绑定到您在获取地址时指定的类。这使我们能够绕过检查

struct A {
protected:
int a;
};


struct B : A {
// valid: *can* access protected member
static int get(A &x) { return x.*(&B::a); }
};


struct C : A { };

当然,它也适用于 std::stack示例。

void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
return s.*(pillager::c);
}
};


// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}

在派生类中使用 using 声明会更容易,它使成员名称为公共的,并引用基类的成员。

void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
using std::stack<int>::c;
};


// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = s.*(&pillager::c);
}

成员指针和成员指针运算符-> *

#include <stdio.h>
struct A { int d; int e() { return d; } };
int main() {
A* a = new A();
a->d = 8;
printf("%d %d\n", a ->* &A::d, (a ->* &A::e)() );
return 0;
}

因为方法(a-> * & A: : e)()有点像 javascript 中的 Function.call ()

var f = A.e
f.call(a)

对于成员,这有点像使用[]运算符访问

a['d']

很多人都知道 identity/id元函数,但是对于非模板用例来说,它有一个很好的用例: 简化编写声明:

// void (*f)(); // same
id<void()>::type *f;


// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);


// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];


// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;

它极大地帮助解密 C + + 声明!

// boost::identity is pretty much the same
template<typename T>
struct id { typedef T type; };

我发现递归模板安装非常酷:

template<class int>
class foo;


template
class foo<0> {
int* get<0>() { return array; }
int* array;
};


template<class int>
class foo<i> : public foo<i-1> {
int* get<i>() { return array + 1; }
};

I've used that to generate a class with 10-15 functions that return pointers into various parts of an array, since an API I used required one function pointer for each value.

例如,通过递归编写编译器来生成一堆函数

Main ()不需要返回值:

int main(){}

是最短的有效的 C + + 程序。

您可以模板位字段。

template <size_t X, size_t Y>
struct bitfield
{
char left  : X;
char right : Y;
};

我还没有想出这样做的任何目的,但是它确实让我感到惊讶。

我最喜欢的(暂时)是缺乏语义的语句,如 A = B = C。 A 的值基本上是不确定的。

想想这个:

class clC
{
public:
clC& operator=(const clC& other)
{
//do some assignment stuff
return copy(other);
}
virtual clC& copy(const clC& other);
}


class clB : public clC
{
public:
clB() : m_copy()
{
}


clC& copy(const clC& other)
{
return m_copy;
}


private:
class clInnerB : public clC
{
}
clInnerB m_copy;
}

现在 A 可能是一个类型,除了 clB 类型的对象之外,其他任何对象都无法访问它,并且它的值与 C 无关。

Void 函数可以返回 void 值

Little known, but the following code is fine

void f() { }
void g() { return f(); }

还有下面这个长相奇怪的

void f() { return (void)"i'm discarded"; }

了解了这一点,你就可以在某些方面获得优势。一个例子: void函数不能返回值,但是也不能什么都不返回,因为它们可能被实例化为 non-void。不需要将值存储到局部变量中(这将导致 void出错) ,只需直接返回一个值

template<typename T>
struct sample {
// assume f<T> may return void
T dosomething() { return f<T>(); }


// better than T t = f<T>(); /* ... */ return t; !
};

防止逗号运算符调用运算符重载

有时,您可以有效地使用逗号操作符,但是您希望确保没有用户定义的逗号操作符会妨碍您的工作,因为例如,您依赖于左边和右边之间的序列点,或者希望确保没有任何东西干扰所需的操作。这就是 void()进入游戏的地方:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);

忽略我为条件和代码放置的占位符。重要的是 void(),它使编译器强制使用内置的逗号运算符。在实现 trait 类时,这有时也很有用。

可以使用某些编译器通过命令行开关查看所有预定义的宏。这适用于 gcc 和 icc (Intel 的 C + + 编译器) :

$ touch empty.cpp
$ g++ -E -dM empty.cpp | sort >gxx-macros.txt
$ icc -E -dM empty.cpp | sort >icx-macros.txt
$ touch empty.c
$ gcc -E -dM empty.c | sort >gcc-macros.txt
$ icc -E -dM empty.c | sort >icc-macros.txt

For MSVC they are listed in a 单人间. They could be documented in a single place for the others too, but with the above commands you can clearly what is and isn't defined and exactly what values are used, after applying all of the other command-line switches.

Compare (after sorting):

 $ diff gxx-macros.txt icx-macros.txt
$ diff gxx-macros.txt gcc-macros.txt
$ diff icx-macros.txt icc-macros.txt

The ternary conditional operator ?: requires its second and third operand to have "agreeable" types (speaking informally). But this requirement has one exception (pun intended): either the second or third operand can be a throw expression (which has type void), regardless of the type of the other operand.

换句话说,可以使用 ?:运算符编写以下完全有效的 C + + 表达式

i = a > b ? a : throw something();

顺便说一句,throw 表达式实际上是 一种表达方式(类型为 void)而不是语句,这是 C + + 语言的另一个鲜为人知的特性。这意味着,除了其他事情之外,下面的代码是完全有效的

void foo()
{
return throw something();
}

尽管这样做没有多大意义(也许在一些通用模板代码中这会很方便)。

  1. 如果键值已经存在,则 map::insert(std::pair(key, value));不会覆盖。

  2. 您可以在类的定义之后立即实例化它: (我可以补充说,由于缺少分号,这个特性给我带来了数百个编译错误,我从未见过任何人在类上使用这个特性)

    class MyClass {public: /* code */} myClass;
    

我认识一个人,他同时用一个方法定义了 getter 和 setter,比如:

class foo
{
int x;


int* GetX(){
return &x;
}
}

You can now use this as a getter as usual (well, almost):

int a = *GetX();

and as a setter:

*GetX() = 17;

模板超编程是。

Not actually a hidden feature, but pure awesomeness:

#define private public

你可以将变量引用作为函数的一部分返回,它有一些用途,主要是用于生成可怕的代码:

int s ;
vector <int> a ;
vector <int> b ;


int &G(int h)
{
if ( h < a.size() ) return a[h] ;
if ( h - a.size() < b.size() ) return b[ h - a.size() ] ;
return s ;
}


int main()
{
a = vector <int> (100) ;
b = vector <int> (100) ;


G( 20) = 40 ; //a[20] becomes 40
G(120) = 40 ; //b[20] becomes 40
G(424) = 40 ; //s becomes 40
}

本地课程棒极了:

struct MyAwesomeAbstractClass
{ ... };




template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
struct ans : MyAwesomeAbstractClass
{
// Make the implementation depend on T
};


return new ans(...);
}

非常整洁,因为它不会用无用的类定义污染名称空间..。

一个隐藏的特性,甚至隐藏到 海湾合作委员会开发商,是使用字符串文字初始化数组成员。假设您有一个需要使用 C 数组的结构,并且希望使用默认内容初始化数组成员

struct Person {
char name[255];
Person():name("???") { }
};

这种方法可以工作,并且只适用于字符数组和字符串文字初始值设定项。不需要 strcpy

另一个隐藏的特性是,您可以调用可以转换为函数指针或引用的类对象。重载解析是对它们的结果进行的,参数被完美地转发。

template<typename Func1, typename Func2>
class callable {
Func1 *m_f1;
Func2 *m_f2;


public:
callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
operator Func1*() { return m_f1; }
operator Func2*() { return m_f2; }
};


void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }


int main() {
callable<void(int), void(long)> c(foo, bar);
c(42); // calls foo
c(42L); // calls bar
}

这些函数称为“代理调用函数”。

另一个在 C 语言中不起作用的隐藏特性是一元 +操作符的功能。你可以用它来促进和衰退各种各样的东西

将枚举转换为整数

+AnEnumeratorValue

以前具有枚举类型的枚举数值现在具有适合其值的完美整数类型。手动的,你几乎不会知道那种类型!例如,当您希望为枚举实现重载运算符时,就需要这样做。

从变量中获取值

你必须使用一个类,使用一个类内静态初始值设定项没有一个类外定义,但有时它不能链接?操作符可以帮助创建临时类型,而不需要对其类型进行假设或依赖

struct Foo {
static int const value = 42;
};


// This does something interesting...
template<typename T>
void f(T const&);


int main() {
// fails to link - tries to get the address of "Foo::value"!
f(Foo::value);


// works - pass a temporary value
f(+Foo::value);
}

将数组衰减为指针

Do you want to pass two pointers to a function, but it just won't work? The operator may help

// This does something interesting...
template<typename T>
void f(T const& a, T const& b);


int main() {
int a[2];
int b[3];
f(a, b); // won't work! different values for "T"!
f(+a, +b); // works! T is "int*" both time
}

统治规则是有用的,但鲜为人知。它指出,即使在通过基类格子的非唯一路径中,如果部分隐藏成员属于虚拟基类,那么该成员的名称查找也是唯一的:

struct A { void f() { } };


struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };


// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };

我把这个应用到 实施校准-支持中,它会自动计算出最严格的排列方式,通过支配规则。

这不仅适用于虚函数,还适用于 typedef 名称、静态/非虚成员和其他任何东西。我见过它在元程序中实现可覆盖的 trait。