C ++的隐藏特性?

没有C ++的爱情,当涉及到“隐藏的特征”这一系列问题时? 我想把它扔在那里。 C ++的一些隐藏function是什么?

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

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

然而,他们并没有意识到它可以被用作左翼:

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

这是速记

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

谨慎使用:-)

您可以将URI放入C ++源代码中,而不会出错。 例如:

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

指针algorithm。

由于可以引入的错误,C ++程序员更喜欢避免使用指针。

我见过的最酷的C ++? 模拟文字。

我同意那里的大多数post:C ++是一个多范式的语言,所以你会发现“隐藏”的function(除了“不确定的行为”,你应该避免不惜一切代价)巧妙地使用设施。

这些设施大多不是语言的内置function,而是基于图书馆的function。

最重要的是RAII ,经常被来自C世界的C ++开发者忽略。 运算符重载通常是一种被误解的特性,它既支持类似数组的行为(下标操作符),也支持类似操作的指针(类指针)和类内置操作(乘以matrix)。

exception的使用通常是困难的,但是通过一些工作,可以通过exception安全规范(包括不会失败的代码,或者具有类似提交的function,将会成功或返回其原始状态)。

C ++中最着名的“隐藏”特性是模板元编程 ,因为它使您能够在编译时(而不是运行时)部分(或完全)执行程序。 然而,这很难,在试用之前,你必须牢牢掌握模板。

其他使用多重范式来产生C ++祖先之外的“编程方式”,即C

通过使用函数 ,你可以模拟函数,附加的types安全和有状态。 使用命令模式,可以延迟代码执行。 大多数其他devise模式可以用C ++轻松高效地实现,以产生不应被列入“官方C ++范例”列表的替代编码风格。

通过使用模板 ,您可以生成适用于大多数types的代码,包括最初的代码。 你也可以增加types安全(就像一个自动types安全的malloc / realloc / free)。 C ++对象特性是非常强大的(因此,如果不小心使用的话,这是非常危险的),但是即使是dynamic多态也有C ++的静态版本: CRTP

我发现大多数Scott Meyers的“ Effective C ++ ”types的书籍或者Herb Sutter的“ Exceptional C ++ ”types的书籍都易于阅读,而且相当宝贵的关于C ++的已知和较less已知特性的信息。

我最喜欢的应该是让任何Java程序员的头发都从恐怖中崛起:在C ++中, 向对象添加特征的最面向对象的方式是通过非成员非友元函数,而不是成员 -函数 (即类方法),因为:

  • 在C ++中,一个类的接口既是它的成员函数,也是同一个名字空间中的非成员函数

  • 非好友非成员函数没有特权访问类内部。 因此,使用成员函数而不是非成员非友方会减弱类的封装。

即使有经验的开发人员也不会惊奇。

(来源:Herb Sutter的本周第84号在线导师: http : //www.gotw.ca/gotw/084.htm )

一种语言特征,我认为是有点隐藏的,因为我从来没有听说过它在我的整个学校,是命名空间的别名。 直到我在boost文档中遇到它的例子时才引起我的注意。 当然,现在我知道了,你可以在任何标准的C ++参考中find它。

 namespace fs = boost::filesystem; fs::path myPath( strPath, fs::native ); 

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

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

这允许不同types的多个variables。

数组运算符是关联的。

A [8]是*(A + 8)的同义词。 由于加法是关联的,所以可以被重写为*(8 + A),它是…的同义词[8]

你没说有用… 🙂

有一点是不为人知的是工会也可以是模板:

 template<typename From, typename To> union union_cast { From from; To to; union_cast(From from) :from(from) { } To getTo() const { return to; } }; 

他们也可以有构造函数和成员函数。 与inheritance(包括虚函数)无关。

C ++是一个标准,不应该有任何隐藏的function…

C ++是一个多范式的语言,你可以把你最后的钱投入到隐藏的function上。 一个很好的例子: 模板元编程 。 标准委员会中的任何人都不打算在编译期间实现图灵完备子语言。

另一个在C中不起作用的隐藏function是一元+运算符的function。 你可以用它来促进和衰减各种各样的东西

将枚举转换为整数

 +AnEnumeratorValue 

而你以前的枚举types的枚举值现在有一个完美的整数types,可以适合它的值。 手工,你几乎不知道这种types! 例如,当你想为你的枚举实现一个重载的操作符时,这是需要的。

从variables中获取值

你必须使用一个使用类内静态初始化方法的类,而不需要类外定义,但是有时却无法链接? 操作员可以帮助创build一个临时的,而不需要对其types进行假设或依赖

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

将数组衰减到一个指针

你想传递一个函数的两个指针,但它不会工作? 操作员可能会帮忙

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

临时参考的生命周期是很less有人知道的。 或者至less是大多数人不知道的我最喜欢的C ++知识。

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

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

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

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

许多人都知道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; }; 

一个相当隐藏的特性是,你可以在if条件中定义variables,它的作用域将只覆盖if和其它块:

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

一些macros使用,例如提供一些像这样的“locking”范围:

 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使用它在引擎盖下。 要做到这一点,不仅可以在一个开关中,而且在一个开关中:

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

并在一个while循环中:

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

(也在条件)。 但我不太确定这些都是有用的:)

防止逗号操作符调用操作符重载

有时你可以有效地使用逗号运算符,但是你想要确保没有用户自定义的逗号运算符,因为例如你依靠左右侧的序列点,或者想要确保没有任何干扰行动。 这是void()进入游戏的地方:

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

忽略我为条件和代码放置的地方。 最重要的是void() ,它使编译器强制使用内置的逗号运算符。 这在实现traits类时也是有用的,有时候也是如此。

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

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

我们可以在构造函数中将数组中的所有元素初始化为默认值(这里是数组的所有元素),如下所示:

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

哦,我可以拿出一个宠物讨厌的名单,而不是:

  • 如果打算使用多态,析构函数需要是虚拟的
  • 有时会员默认初始化,有时候不会
  • 本地clas不能用作模板参数(使它们不太有用)
  • exception说明符:看起来有用,但不是
  • 函数重载隐藏不同签名的基类函数。
  • 没有有用的标准化国际化(便携式标准宽字符集,任何人?我们将不得不等到C ++ 0x)

从积极的一面

  • 隐藏function:function试块。 不幸的是我没有find它的用途。 是的,我知道他们为什么添加它,但是你必须重新构造一个毫无意义的构造函数。
  • 在容器修改之后,值得仔细查看关于迭代器有效性的STL保证,这可以让你做出一些稍好的循环。
  • 提升 – 这不是什么秘密,但值得使用。
  • 返回值优化(不是显而易见的,但它是标准特别允许的)
  • 函数又称函数对象又名运算符()。 这被STL广泛使用。 不是真的秘密,而是运算符重载和模板的一个漂亮的副作用。

您可以访问受保护的数据和任何类的函数成员,而不会有未定义的行为,并具有预期的语义。 继续阅读,看看如何。 另请阅读关于此的缺陷报告 。

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

 struct A { protected: int a; }; struct B : A { // error: can't access protected member static int get(A &x) { return xa; } }; 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 sc; } }; // haha, now let's inspect the stack's middle elements! std::deque<int> &d = pillager::get(s); } 

当然,如你所见,这会造成太多的伤害。 但现在,成员指针允许绕过这种保护! 关键是成员指针的types绑定到实际包含该成员的类 – 而不是您在获取地址时指定的类。 这使我们能够规避检查

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

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

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

另一个隐藏的function是可以调用可以转换为函数指针或引用的类对象。 重载parsing是在它们的结果上完成的,参数是完美转发的。

 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 } 

这些被称为“代理呼叫function”。

隐藏function:

  1. 纯虚函数可以有实现。 常见的例子,纯粹的虚拟析构函数。
  2. 如果函数抛出exception规范中未列出的exception,但函数在其exception规范中有std::bad_exception ,则该exception将转换为std::bad_exception并自动抛出。 这样你至less会知道一个错误的抛出。 在这里阅读更多。

  3. function尝试块

  4. 类模板中用于消除歧义types定义的模板关键字。 如果成员模板专业化的名称出现在a之后.->::运算符,并且该名称具有显式限定的模板参数,则使用关键字模板将成员模板名称作为前缀。 在这里阅读更多。

  5. 函数参数的默认值可以在运行时改变。 在这里阅读更多。

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

  7. 一个类的临时实例可以修改! 可以在临时对象上调用非const成员函数。 例如:

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

    在这里阅读更多。

  8. 如果在三元( ?: :)运算符expression式之前和之后存在两种不同的types,则expression式的结果types是两者中最一般的那种types。 例如:

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

map::operator[]创build条目,如果键缺失并返回引用默认构造的条目值。 所以你可以写:

 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; 

我惊讶于有多lessC ++程序员不知道这一点。

将函数或variables放在无名称的命名空间中,不赞成使用static来将它们限制在文件范围内。

Defining ordinary friend functions in class templates needs special attention:

 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! 

In this example, two different instantiations create two identical definitions—a direct violation of the ODR

We must therefore make sure the template parameters of the class template appear in the type of any friend function defined in that template (unless we want to prevent more than one instantiation of a class template in a particular file, but this is rather unlikely). Let's apply this to a variation of our previous example:

 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>*) 

Disclaimer: I have pasted this section from C++ Templates: The Complete Guide / Section 8.4

void functions can return void values

Little known, but the following code is fine

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

Aswell as the following weird looking one

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

Knowing about this, you can take advantage in some areas. One example: void functions can't return a value but you can also not just return nothing, because they may be instantiated with non-void. Instead of storing the value into a local variable, which will cause an error for void , just return a value directly

 template<typename T> struct sample { // assume f<T> may return void T dosomething() { return f<T>(); } // better than T t = f<T>(); /* ... */ return t; ! }; 

Read a file into a vector of strings:

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

istream_iterator

You can template bitfields.

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

I have yet to come up with any purpose for this, but it sure as heck surprised me.

One of the most interesting grammars of any programming languages.

Three of these things belong together, and two are something altogether different…

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

All but the third and fifth define a SomeType object on the stack and initialize it (with u in the first two case, and the default constructor in the fourth. The third is declaring a function that takes no parameters and returns a SomeType . The fifth is similarly declaring a function that takes one parameter by value of type SomeType named u .

Getting rid of forward declarations:

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

Writing switch-statements with ?: operators:

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

Doing everything on a single line:

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

Zeroing structs without memset:

 FStruct s = {0}; 

Normalizing/wrapping angle- and time-values:

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

Assigning references:

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

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.

In other words, one can write the following pefrectly valid C++ expressions using the ?: operator

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

BTW, the fact that throw expression is actually an expression (of type void ) and not a statement is another little-known feature of C++ language. This means, among other things, that the following code is perfectly valid

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

although there's not much point in doing it this way (maybe in some generic template code this might come handy).

The dominance rule is useful, but little known. It says that even if in a non-unique path through a base-class lattice, name-lookup for a partially hidden member is unique if the member belongs to a virtual base-class:

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

I've used this to implement alignment-support that automatically figures out the strictest alignment by means of the dominance rule.

This does not only apply to virtual functions, but also to typedef names, static/non-virtual members and anything else. I've seen it used to implement overwritable traits in meta-programs.