当涉及到"问题线"的"隐藏特征"时,没有C++的爱吗?想我会把它扔出去.C++的一些隐藏功能是什么?
大多数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; ... }
指针算术.
由于可以引入的错误,C++程序员更喜欢避免使用指针.
我见过的最酷的C++?模拟文字.
我同意那里的大多数帖子:C++是一种多范式语言,所以你会发现的"隐藏"功能(除了"不定义的行为",你应该不惜一切代价避免)是聪明的设施使用.
大多数这些设施不是语言的内置功能,而是基于库的功能.
最重要的是RAII,多年来C++开发人员经常忽略来自C世界.运算符重载通常是一个误解的特性,它支持类似数组的行为(下标运算符),类似指针的操作(智能指针)和类似内置的操作(乘法矩阵).
使用异常通常很困难,但通过一些工作,可以通过异常安全规范生成非常强大的代码(包括不会失败的代码,或者具有类似提交功能的代码,或者恢复为它的原始状态).
C++中最着名的"隐藏"功能是模板元编程,因为它使您可以在编译时而不是运行时部分(或完全)执行程序.但这很困难,在尝试之前,您必须牢牢掌握模板.
其他人利用多范式在C++的祖先之外产生"编程方式",即C.
通过使用仿函数,你可以模拟功能,具有额外的类型安全并且是有状态的.使用命令模式,可以延迟代码执行.大多数其他设计模式可以在C++中轻松有效地实现,以产生不应该在"官方C++范例"列表中的替代编码样式.
通过使用模板,您可以生成适用于大多数类型的代码,包括不是您最初想到的代码.您也可以增加类型安全性(如自动类型安全malloc/realloc/free).C++对象的功能非常强大(因此,如果不小心使用会很危险),但即使是动态多态也有其在C++中的静态版本:CRTP.
我发现,来自Scott Meyers的大多数" Effective C++ "类书籍或来自Herb Sutter的" Exceptional C++ "类书籍都易于阅读,并且对C++的已知和鲜为人知的特性有很多信息.
在我的首选中,应该让任何Java程序员的头发从恐怖中崛起:在C++中,向对象添加功能的最面向对象的方式是通过非成员非朋友功能,而不是成员 -函数(即类方法),因为:
在C++中,类的接口既是其成员函数,也是同一名称空间中的非成员函数
非朋友非成员函数没有对内部类的特权访问.因此,对非成员非成员使用成员函数会削弱类的封装.
这甚至不会让经验丰富
(资料来源:Herb Sutter的本周在线大师#84:http://www.gotw.ca/gotw/084.htm)
我认为有些隐藏的一种语言功能,因为我在学校的整个过程中从未听说过它,是名称空间别名.直到我在boost文档中遇到它的例子之后才引起我的注意.当然,现在我了解它,你可以在任何标准的C++参考中找到它.
namespace fs = boost::filesystem; fs::path myPath( strPath, fs::native );
变量不仅可以在for
循环的init部分声明,还可以声明类和函数.
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) { ... }
这允许多个不同类型的变量.
数组运算符是关联的.
A [8]是*(A + 8)的同义词.由于加法是关联的,可以改写为*(8 + A),这是..... 8 [A]的同义词
你没说有用...... :-)
有一点鲜为人知的是,工会也可以是模板:
templateunion union_cast { From from; To to; union_cast(From from) :from(from) { } To getTo() const { return to; } };
他们也可以拥有构造函数和成员函数.没有任何与继承(包括虚函数)有关的事情.
C++是一个标准,不应该有任何隐藏的功能......
C++是一种多范式语言,你可以把最后的钱押在那里隐藏的功能.许多例子中的一个例子:模板元编程.标准委员会中没有人打算在编译时执行图灵完整的子语言.
另一个在C中不起作用的隐藏功能是一元运算+
符的功能.你可以用它来促进和腐朽各种各样的事物
+AnEnumeratorValue
以前具有枚举类型的枚举器值现在具有可以适合其值的完美整数类型.手动,你很难知道那种类型!例如,当您想为枚举实现重载运算符时,需要这样做.
您必须使用一个使用类内静态初始化程序而没有类外定义的类,但有时它无法链接?操作员可以帮助创建一个临时的,而不会对其类型产生任何假设或依赖
struct Foo { static int const value = 42; }; // This does something interesting... templatevoid 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... templatevoid 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 }
与const引用相关的临时工的生命期是很少有人知道的.或者至少它是我最喜欢的C++知识,大多数人都不知道.
const MyClass& x = MyClass(); // temporary exists as long as x is in scope
一个不常用的好功能是功能范围的try-catch块:
int Function() try { // do something here return 42; } catch(...) { return -1; }
主要用法是将异常转换为其他异常类和重新抛出,或者在异常和基于返回的错误代码处理之间进行转换.
很多人都知道identity
/ id
metafunction,但对于非模板情况,它有一个很好的用例:轻松编写声明:
// void (*f)(); // same id::type *f; // void (*f(void(*p)()))(int); // same id ::type *f(id ::type *p); // int (*p)[2] = new int[10][2]; // same id ::type *p = new int[10][2]; // void (C::*p)(int) = 0; // same id ::type C::*p = 0;
它有助于大大解密C++声明!
// boost::identity is pretty much the same templatestruct id { typedef T type; };
一个非常隐蔽的功能是您可以在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(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() { }
哦,我可以提出一个宠物仇恨列表:
如果您打算使用多态,则析构函数必须是虚拟的
有时会默认初始化成员,有时则不会
本地clases不能用作模板参数(使它们不那么有用)
异常说明符:看起来很有用,但不是
函数重载隐藏具有不同签名的基类函数.
国际化没有有用的标准化(便携式标准宽字符集,任何人?我们必须等到C++ 0x)
从积极的一面
隐藏功能:功能尝试块.不幸的是我没有找到它的用途.是的我知道为什么他们添加它,但你必须重新抛出一个构造函数,这使它毫无意义.
在容器修改之后,值得仔细查看STL保证迭代器有效性,这可以让你做一些稍好的循环.
提升 - 这不是什么秘密,但它值得使用.
返回值优化(不明显,但标准特别允许)
Functors aka function objects aka operator().这被STL广泛使用.不是真正的秘密,但是操作符重载和模板的一个漂亮的副作用.
您可以访问任何类的受保护数据和函数成员,没有未定义的行为,并具有预期的语义.继续阅读,看看如何.另请阅读有关此问题的缺陷报告.
通常,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&s) { // now, let's decide to mess with that stack! struct pillager : std::stack { static std::deque &get(std::stack &s) { // error: stack ::c is protected return s.c; } }; // haha, now let's inspect the stack's middle elements! std::deque &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&s) { // now, let's decide to mess with that stack! struct pillager : std::stack { static std::deque &get(std::stack &s) { return s.*(pillager::c); } }; // haha, now let's inspect the stack's middle elements! std::deque &d = pillager::get(s); }
使用派生类中的using声明会更容易,这会使成员名称为public并引用基类的成员.
void f(std::stack&s) { // now, let's decide to mess with that stack! struct pillager : std::stack { using std::stack ::c; }; // haha, now let's inspect the stack's middle elements! std::deque &d = s.*(&pillager::c); }
隐藏功能:
纯虚函数可以实现.常见的例子,纯虚拟析构函数.
如果函数抛出未在其异常规范中列出的异常,但该函数std::bad_exception
在其异常规范中有异常,则会std::bad_exception
自动转换并抛出异常.这样你至少会知道a bad_exception
被扔了.在这里阅读更多.
功能尝试块
模板关键字在类模板中消除typedef的歧义.如果成员模板特的名字出现后.
,->
或::
运营商,这个名字有明确限定的模板参数,以关键字template前缀的成员模板名称.在这里阅读更多.
函数参数默认值可以在运行时更改.在这里阅读更多.
A[i]
工作得很好 i[A]
可以修改类的临时实例!可以在临时对象上调用非const成员函数.例如:
struct Bar { void modify() {} } int main (void) { Bar().modify(); /* non-const function invoked on a temporary. */ }
在这里阅读更多.
如果:
在ternary(?:
)运算符表达式之前和之后存在两种不同的类型,则表达式的结果类型是两者中最常用的类型.例如:
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) }
另一个隐藏的功能是您可以调用可以转换为函数指针或引用的类对象.对它们的结果进行重载分辨率,并且完全转发参数.
templateclass 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 c(foo, bar); c(42); // calls foo c(42L); // calls bar }
这些被称为"代理呼叫功能".
map::operator[]
如果缺少键则创建条目并返回对缺省构造的条目值的引用.所以你可以写:
mapm; 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
它们来限制它们到文件范围.
在类模板中定义普通的朋友函数需要特别注意:
templateclass Creator { friend void appear() { // a new function ::appear(), but it doesn't … // exist until Creator is instantiated } }; Creator miracle; // ::appear() is created at this point Creator oops; // ERROR: ::appear() is created a second time!
在此示例中,两个不同的实例创建两个相同的定义 - 直接违反ODR
因此,我们必须确保类模板的模板参数出现在该模板中定义的任何友元函数的类型中(除非我们想要阻止特定文件中多个类模板的实例化,但这是不太可能的).让我们将其应用于前一个示例的变体:
templateclass Creator { friend void feed(Creator *){ // every T generates a different … // function ::feed() } }; Creator one; // generates ::feed(Creator *) Creator two; // generates ::feed(Creator *)
免责声明:我已经从C++模板:完整指南 /第8.4 节粘贴了这一部分
鲜为人知,但下面的代码很好
void f() { } void g() { return f(); }
以及下面奇怪的一个
void f() { return (void)"i'm discarded"; }
了解这一点,你可以在某些方面利用.一个例子:void
函数不能返回一个值,但你也不能只返回任何值,因为它们可以用非void实例化.不是将值存储到将导致错误的局部变量中,而是void
直接返回值
templatestruct sample { // assume f may return void T dosomething() { return f (); } // better than T t = f (); /* ... */ return t; ! };
将文件读入字符串向量:
vectorV; copy(istream_iterator (cin), istream_iterator (), back_inserter(V));
istream_iterator
任何编程语言中最有趣的语法之一.
这些东西中的三个属于一起,两个是完全不同的东西......
SomeType t = u; SomeType t(u); SomeType t(); SomeType t; SomeType t(SomeType(u));
除了第三个和第五个之外的所有SomeType
对象都在堆栈上定义并初始化它(u
在前两种情况下,在第四种情况下使用默认构造函数.第三种是声明一个不带参数并返回a的函数SomeType
.第五种类似于声明一个函数,它通过SomeType
命名类型的值获取一个参数u
.
您可以模板位域.
templatestruct bitfield { char left : X; char right : Y; };
我还没有为此提出任何目的,但确实让我感到惊讶.
摆脱前瞻性声明:
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;
三元条件运算符?:
要求其第二和第三操作数具有"令人愉快"的类型(非正式地说).但是这个要求有一个例外(双关语):第二个或第三个操作数可以是一个throw表达式(有类型void
),而不管另一个操作数的类型.
换句话说,可以使用?:
运算符编写以下有效的C++表达式
i = a > b ? a : throw something();
BTW,throw表达式实际上是一个表达式(类型void
)而不是语句这一事实是C++语言的另一个鲜为人知的特性.这意味着,以下代码完全有效
void foo() { return throw something(); }
虽然这样做没有多大意义(可能在一些通用的模板代码中,这可能会派上用场).
优势规则很有用,但鲜为人知.它表示即使在通过基类网格的非唯一路径中,如果成员属于虚拟基类,则部分隐藏成员的名称查找也是唯一的:
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名称,静态/非虚拟成员等.我已经看到它曾用于在元程序中实现可重写的特性.
我发现这个博客是一个关于C++:C++ Truths的神奇资源.
一个危险的秘密是
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 }
本地课程很棒:
struct MyAwesomeAbstractClass { ... }; templateMyAwesomeAbstractClass* create_awesome(T param) { struct ans : MyAwesomeAbstractClass { // Make the implementation depend on T }; return new ans(...); }
非常整洁,因为它没有用无用的类定义污染命名空间......
原始类型具有构造函数.
int i(3);
作品.
一个隐藏的功能,甚至是GCC开发人员隐藏的功能,是使用字符串文字初始化数组成员.假设您有一个需要使用C数组的结构,并且您希望使用默认内容初始化该数组成员
struct Person { char name[255]; Person():name("???") { } };
这有效,并且只适用于char数组和字符串文字初始值设定项.不需要strcpy
!
许多例子中的一个例子:模板元编程.标准委员会中没有人打算在编译时执行图灵完整的子语言.
模板元编程几乎不是隐藏的功能.它甚至在升级库中.见MPL.但如果"几乎隐藏"足够好,那么看一下boost库.它包含许多好东西,没有强大的库的支持,不容易访问.
一个例子是boost.lambda库,这很有趣,因为C++在当前标准中没有lambda函数.
另一个例子是Loki,它"广泛使用C++模板元编程并实现了几种常用的工具:类型列表,仿函数,单例,智能指针,对象工厂,访问者和多方法." [ 维基百科 ]
没有隐藏的功能,但C++语言非常强大,甚至标准的开发人员也无法想象C++可以用于什么.
实际上,从简单的语言构造,你可以写出非常强大的东西.很多这样的东西可以在www.boost.org上找到(例如http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html).
为了理解简单的语言结构如何与强大的东西结合起来,最好阅读David Vandevoorde,Nicolai M. Josuttis撰写的" C++模板:完整指南",以及Andrei Alexandrescu的真实魔法书"现代C++设计......".
最后,学习C++很难,你应该尝试填充它;)