Hidden Features of C++?
当涉及到问题的"隐藏特征"时,没有C++的爱吗?我想我会把它扔出去。C++的一些隐藏的特性是什么?
大多数C++程序员都熟悉三元运算符:
1 | x = (y < 0) ? 10 : 20; |
但是,他们没有意识到它可以用作左值:
1 | (a == 0 ? a : b) = 1; |
哪个是缩写
1 2 3 4 | if (a == 0) a = 1; else b = 1; |
小心使用:-)
可以将URI添加到C++源中,不会出错。例如:
1 2 3 4 5 6 | void foo() { http://stackoverflow.com/ int bar = 4; ... } |
Pointer arithmetics.
C++程序员喜欢避免指针,因为可以引入错误。
我见过的最酷的C++?模拟文字。
我同意大多数帖子:C++是一种多范式语言,所以你会发现的"隐藏"特性(除了"不确定的行为",你应该不惜一切代价避免)都是设施的巧妙使用。
这些设施中的大多数不是语言的内置功能,而是基于库的功能。
最重要的是RAI,经常被C++开发者从C世界忽略多年。运算符重载通常是一个被误解的特性,它支持类似数组的行为(下标运算符)、类似指针的操作(智能指针)和内置类似操作(乘法矩阵)。
异常的使用通常是困难的,但通过一些工作,可以通过异常安全规范(包括不会失败的代码,或者具有类似提交的功能(即成功的功能),或者恢复到原始状态)生成真正健壮的代码。
C++中最著名的"隐藏"特性是模板元编程,因为它可以让你的程序部分地(或全部)在编译时执行,而不是运行时执行。不过,这很困难,在尝试之前,您必须对模板有一个扎实的理解。
其他的使用多个范例来产生C++的祖先之外的"编程方式",即C.。
通过使用函数,您可以模拟具有附加类型安全性和状态性的函数。使用命令模式,可以延迟代码执行。大多数其他设计模式可以在C++中轻松高效地实现,而不是在"官方C++范式"列表中产生可替代的编码风格。
通过使用模板,您可以生成适用于大多数类型的代码,包括您最初认为不适用的代码。您也可以提高类型安全性(如自动类型安全malloc/realloc/free)。C++对象特性非常强大(因此,如果不小心使用,则是危险的),但是即使动态多态性也有C++中的静态版本:CRTP。
我发现,大多数"有效的C++"类型的书来自Scott Meyers或"Excel C++"类型的书从赫伯萨特,都是容易阅读,以及相当珍惜的信息已知和不太知名的特点,C++。
在我的首选中,一个应该使任何Java程序员的头发从恐怖中崛起:在C++中,向对象添加特征的最面向对象的方式是通过非成员非朋友函数,而不是成员函数(即类方法),因为:
在C++中,一个类的接口既是它的成员函数又是同一个命名空间中的非成员函数。
非友元非成员函数无权访问类内部。因此,对非成员非友元使用成员函数会削弱类的封装。
即使是经验丰富的开发人员也不会因此而感到惊讶。
(资料来源:Herb Sutter的《本周在线大师》84:http://www.gotw.ca/gotw/084.htm)
我认为有一个语言特性是名称空间别名,因为我在学校的整个时间里都没有听说过它。直到我在Boost文档中遇到它的例子,它才引起我的注意。当然,现在我知道了,你可以在任何标准的C++引用中找到它。
1 2 3 | namespace fs = boost::filesystem; fs::path myPath( strPath, fs::native ); |
不仅可以在
1 2 3 | for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) { ... } |
允许不同类型的多个变量。
数组运算符是关联的。
A[8]是*(A+8)的同义词。由于加法是关联的,可以重写为*(8+A),这是…..的同义词。8〔A〕
你没说有用…-)
一件鲜为人知的事情是工会也可以是模板:
1 2 3 4 5 6 7 8 9 10 | template<typename From, typename To> union union_cast { From from; To to; union_cast(From from) :from(from) { } To getTo() const { return to; } }; |
它们也可以有构造函数和成员函数。只是与继承(包括虚拟函数)无关。
C++ is a standard, there shouldn't be any hidden features...
C++是一种多范例语言,你可以把你的最后一笔钱押在隐藏的特征上。其中一个例子是:模板元编程。标准委员会中没有人打算在编译时执行图灵完整的子语言。
另一个在C中不起作用的隐藏特性是一元
1 | +AnEnumeratorValue |
以前有枚举类型的枚举器值现在有了完美的整数类型,可以满足它的值。手动操作,你很难知道那种类型!例如,当您希望为枚举实现重载运算符时,就需要这样做。
从变量中获取值必须使用没有类外定义的类内静态初始值设定项的类,但有时它无法链接?运算符可以帮助创建临时的,而不必对其类型进行假设或依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); } |
将数组衰减为指针
你想给一个函数传递两个指针,但它不能工作吗?接线员可以帮忙
1 2 3 4 5 6 7 8 9 10 | // 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 } |
与常量引用绑定的临时变量的生命周期是很少有人知道的。或者至少是我最喜欢的C++知识,大多数人都不知道。
1 | const MyClass& x = MyClass(); // temporary exists as long as x is in scope |
一个不常用的好特性是函数范围的Try-Catch块:
1 2 3 4 5 6 7 8 9 10 | int Function() try { // do something here return 42; } catch(...) { return -1; } |
主要用途是将异常转换为其他异常类并重新引发,或者在异常和基于返回的错误代码处理之间进行转换。
许多人知道
1 2 3 4 5 6 7 8 9 10 11 | // 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++声明!
1 2 3 | // boost::identity is pretty much the same template<typename T> struct id { typedef T type; }; |
一个相当隐藏的特性是,您可以在一个if条件中定义变量,它的作用域将只跨越if和它的else块:
1 2 3 | if(int * p = getPointer()) { // do something } |
一些宏使用它,例如提供一些"锁定"的作用域,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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) { /* ... */ } } |
也可以在引擎盖下使用它。要完成此操作,不仅可以在if中进行,还可以在开关中进行:
1 2 3 | switch(int value = getIt()) { // ... } |
在while循环中:
1 2 3 | while(SomeThing t = getSomeThing()) { // ... } |
(也在for条件下)。但我不太确定这些是否都有用。)
防止逗号运算符调用运算符重载
有时,您会有效地使用逗号运算符,但您希望确保没有用户定义的逗号运算符妨碍您的工作,因为例如,您依赖于左侧和右侧之间的序列点,或者希望确保没有任何内容干扰到所需的操作。这就是
1 2 | for(T i, j; can_continue(i, j); ++i, void(), ++j) do_code(i, j); |
忽略我为条件和代码而放置的占位符。重要的是
构造函数中的数组初始化。例如,在一个类中,如果我们有一个
1 2 3 4 5 | class clName { clName(); int a[10]; }; |
我们可以在构造函数中将数组中的所有元素初始化为其默认值(此处,数组的所有元素都为零),如下所示:
1 2 3 | clName::clName() : a() { } |
您可以访问任何类的受保护数据和函数成员,而无需定义行为,并且具有预期的语义。继续读下去看看怎么做。同时阅读关于这个的缺陷报告。
通常,C++禁止您访问类对象的非静态保护成员,即使该类是您的基类。
1 2 3 4 5 6 7 8 9 10 11 | 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 { }; |
这是禁止的:您和编译器不知道引用实际指向什么。它可能是一个
1 2 3 4 5 6 7 8 9 10 11 12 | 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); } |
当然,正如你所见,这会造成太多的损害。但是现在,成员指针允许绕过这种保护!关键点是成员指针的类型绑定到实际包含所述成员的类,而不是您在获取地址时指定的类。这样我们就可以绕过检查
1 2 3 4 5 6 7 8 9 10 11 | 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 { }; |
当然,它也适用于
1 2 3 4 5 6 7 8 9 10 11 | 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声明会更容易一些,它使成员名成为公共的,并引用基类的成员。
1 2 3 4 5 6 7 8 9 | 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); } |
哦,我可以列出宠物的憎恨:
- 如果要使用多态性,析构函数必须是虚拟的。
- 有时成员是默认初始化的,有时不是
- 局部类不能用作模板参数(使它们不那么有用)
- 异常说明符:看起来很有用,但不是
- 函数重载隐藏具有不同签名的基类函数。
- 在国际化方面没有有用的标准化(便携标准宽字符集,有人吗?我们必须等到C++0X)
有利的一面
- 隐藏功能:函数尝试块。不幸的是,我没有找到它的用途。是的,我知道他们为什么添加它,但是您必须在一个构造函数中重新执行,这使得它没有意义。
- 值得仔细研究一下STL在修改容器后对迭代器有效性的保证,这可以让您做一些稍微好一点的循环。
- 增强-这不是秘密,但值得使用。
- 返回值优化(不明显,但标准特别允许)
- functors aka function objects aka operator()。这被STL广泛使用。这并不是什么秘密,但却是运算符重载和模板的巧妙副作用。
隐藏功能:
如果函数抛出异常规范中未列出的异常,但该函数的异常规范中有
函数尝试块
类模板中消除typedef歧义的template关键字。如果成员模板专用化的名称出现在
函数参数默认值可以在运行时更改。在这里阅读更多。
可以修改类的临时实例!可以对临时对象调用非常量成员函数。例如:
1 2 3 4 5 6 | struct Bar { void modify() {} } int main (void) { Bar().modify(); /* non-const function invoked on a temporary. */ } |
在这里阅读更多。
如果在三元(
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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) } |
另一个隐藏的特性是,可以调用可转换为函数指针或引用的类对象。重载解析是在它们的结果上完成的,参数是完全转发的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 } |
这些被称为"代理调用函数"。
如果缺少键,
1 2 3 4 5 6 | 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++程序员不知道这一点。
将函数或变量放在无名称的命名空间中会拒绝使用
在类模板中定义普通朋友函数需要特别注意:
1 2 3 4 5 6 7 8 | 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
因此,我们必须确保类模板的模板参数出现在该模板中定义的任何友元函数的类型中(除非我们希望在特定文件中阻止类模板的多个实例化,但这是不太可能的)。让我们将其应用于前面示例的变体:
1 2 3 4 5 6 7 8 9 | 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++模板粘贴了这个部分:完整的指南/章节8.4
void函数可以返回void值
不知道,但下面的代码是好的
1 2 | void f() { } void g() { return f(); } |
还有下面这个看起来很奇怪的
1 | void f() { return (void)"i'm discarded"; } |
知道了这一点,你就可以在某些方面占优势。例如:
1 2 3 4 5 6 7 | template<typename T> struct sample { // assume f<T> may return void T dosomething() { return f<T>(); } // better than T t = f<T>(); /* ... */ return t; ! }; |
将文件读取到字符串向量中:
1 2 3 | vector<string> V; copy(istream_iterator<string>(cin), istream_iterator<string>(), back_inserter(V)); |
Istream_迭代器
任何编程语言中最有趣的语法之一。
这三样东西属于一起,两样东西完全不同…
1 2 3 4 5 | SomeType t = u; SomeType t(u); SomeType t(); SomeType t; SomeType t(SomeType(u)); |
除了第三个和第五个,其余的都在堆栈上定义一个
您可以模板位域。
1 2 3 4 5 6 | template <size_t X, size_t Y> struct bitfield { char left : X; char right : Y; }; |
我还没有为此想出任何目的,但这确实让我吃惊。
优势法则是有用的,但鲜为人知。它表示,即使在通过基类格的非唯一路径中,如果成员属于虚拟基类,则部分隐藏成员的名称查找也是唯一的:
1 2 3 4 5 6 7 | 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名称、静态/非虚拟成员以及其他任何内容。我见过它用于在元程序中实现可重写特性。
取消转发声明:
1 2 3 4 5 6 7 8 9 10 11 | struct global { void main() { a = 1; b(); } int a; void b(){} } singleton; |
用什么写开关语句?经营者:
1 2 3 4 5 | string result = a==0 ?"zero" : a==1 ?"one" : a==2 ?"two" : 0; |
在一条线上做每件事:
1 2 3 | void a(); int b(); float c = (a(),b(),1.0f); |
不带memset的归零结构:
1 | FStruct s = {0}; |
规格化/包装角度和时间值:
1 | int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150 |
分配引用:
1 2 3 4 5 6 7 8 9 | struct ref { int& r; ref(int& r):r(r){} }; int b; ref a(b); int c; *(int**)&a = &c; |
三元条件运算符
换言之,可以使用EDCOX1〔4〕操作符写出下列正确有效的C++表达式。
1 | i = a > b ? a : throw something(); |
顺便说一下,抛出表达式实际上是表达式(EDCOX1(0)类型)而不是语句是C++语言的另一个鲜为人知的特征。这意味着,除其他外,以下代码是完全有效的
1 2 3 4 | void foo() { return throw something(); } |
尽管这样做没有什么意义(也许在一些通用模板代码中,这可能会很方便)。
我发现这个博客是一个惊人的资源,关于奥秘的C++:C++真理。
危险的秘密是
1 2 | Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10 f->~Fred(); |
我最喜欢的秘密我很少看到用过:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 } |
本地课程很棒:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct MyAwesomeAbstractClass { ... }; template <typename T> MyAwesomeAbstractClass* create_awesome(T param) { struct ans : MyAwesomeAbstractClass { // Make the implementation depend on T }; return new ans(...); } |
非常整洁,因为它不会用无用的类定义污染命名空间…
基元类型具有构造函数。
1 | int i(3); |
作品。
一个隐藏的特性,甚至对GCC开发人员来说也是隐藏的,就是使用字符串文字初始化数组成员。假设您有一个需要使用C数组的结构,并且您希望用默认内容初始化数组成员。
1 2 3 4 | struct Person { char name[255]; Person():name("???") { } }; |
这是可行的,并且只适用于char数组和字符串文字初始值设定项。不需要
One example out of many: template
metaprogramming. Nobody in the
standards committee intended there to
be a Turing-complete sublanguage that
gets executed at compile-time.
模板元编程几乎不是隐藏的特性。它甚至在Boost库中。参见MPL。但是,如果"几乎隐藏"足够好,那么看看Boost库。它包含了许多没有强大的图书馆的支持是不容易获得的好东西。
一个例子是BooS.lambda库,这是有趣的,因为C++在当前标准中没有lambda函数。
另一个例子是洛基,它"广泛使用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++设计》中的"C++模板:完整指南"是很好的。安德烈·亚历山大·斯库。
最后,很难学习C++,你应该尝试去填充它;
在我看来,只有少数人知道未命名的名称空间:
1 2 3 | namespace { // Classes, functions, and objects here. } |
未命名的命名空间的行为就像被替换为:
1 2 3 4 5 | namespace __unique_name__ { /* empty body */ } using namespace __unique_name__; namespace __unique_name__ { // original namespace body } |
"…其中翻译单元中的[这个唯一名称]的所有发生被相同的标识符替换,并且这个标识符与整个程序中的所有其他标识符不同。
C++模板可以检查函数的存在吗?
我不确定是否隐藏,但是有一些有趣的"技巧"仅仅从阅读规范中可能不明显。
- 指向类方法的指针
- "typename"关键字
大多数C++开发人员忽略了模板元编程的能力。看看loki libary。它广泛使用模板元编程(来自维基百科)来实现几种高级工具,如类型列表、函数、单例、智能指针、对象工厂、访问者和多方法。在大多数情况下,你可以把它们看作是"隐藏"的C++特性。
从C++真理。
在同一范围内定义具有相同签名的函数,因此这是合法的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | template<class T> // (a) a base template void f(T) { std::cout <<"f(T) "; } template<> void f<>(int*) { // (b) an explicit specialization std::cout <<"f(int *) specilization "; } template<class T> // (c) another, overloads (a) void f(T*) { std::cout <<"f(T *) "; } template<> void f<>(int*) { // (d) another identical explicit specialization std::cout <<"f(int *) another specilization "; } |
throw是一个表达式
有很多"未定义的行为"。你可以学习如何避免他们读好书和阅读标准。
main()不需要返回值:
1 | int main(){} |
是最短的有效C++程序。
如果键值已经存在,
可以在类定义后立即实例化该类:(我可能会补充说,由于缺少分号,这个特性给了我数百个编译错误,而且我从未见过有人在类上使用过这个特性)
1 | class MyClass {public: /* code */} myClass; |
注意自由函数指针和成员函数指针初始化的区别:
成员函数:
1 2 3 4 5 6 7 | struct S { void func(){}; }; int main(){ void (S::*pmf)()=&S::func;// & is mandatory } |
自由功能:
1 2 3 4 | void func(int){} int main(){ void (*pf)(int)=func; // & is unnecessary it can be &func as well; } |
由于这种冗余&;的存在,您可以添加流操作器,这些操作器是链中的自由函数,无需它:
1 | cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat. |
C++中有很多"棘手"的构造。它们来自使用虚拟继承的密封/最终类的"简单"实现。以及非常"复杂"的元编程结构,比如Boost的MPL(教程)。向自己开枪的可能性是无止境的,但如果要保持克制(即经验丰富的程序员),在可维护性和性能方面提供一些最佳的灵活性。
我发现递归模板安装非常酷:
1 2 3 4 5 6 7 8 9 10 11 12 13 | template<class int> class foo; template class foo<0> { int* get<0>() { return array; } int* array; }; template<class int> class foo : public foo<i-1> { int* get() { return array + 1; } }; |
我用它来生成一个类,其中有10-15个函数将指针返回到数组的各个部分,因为我使用的API要求每个值都有一个函数指针。
即通过递归对编译器进行编程以生成一系列函数。易如反掌。:)
如果operator delete()除了接受*void之外还接受了size参数,这意味着它将高度成为一个基类。该大小参数使检查类型的大小成为可能,以便破坏正确的类型。这里是斯蒂芬·德胡斯特所说的:
Notice also that we've employed a
two-argument version of operator
delete rather than the usual
one-argument version. This
two-argument version is another
"usual" version of member operator
delete often employed by base classes
that expect derived classes to inherit
their operator delete implementation.
The second argument will contain the
size of the object being
deleted—information that is often
useful in implementing custom memory
management.
类和结构类键几乎相同。主要的区别在于类默认为成员和基的私有访问,而结构默认为公共访问:
1 2 3 4 5 6 7 8 9 10 11 12 | // 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 |
联合也可以有成员和方法,并且默认为与结构类似的公共访问。
间接转换习语:
Suppose you're designing a smart
pointer class. In addition to
overloading the operators * and ->, a
smart pointer class usually defines a
conversion operator to bool:
1 2 3 4 5 6 7 8 9 10 11 12 | template <class T> class Ptr { public: operator bool() const { return (rawptr ? true: false); } //..more stuff private: T * rawptr; }; |
The conversion to bool enables clients
to use smart pointers in expressions
that require bool operands:
1 2 3 4 5 | Ptr<int> ptr(new int); if(ptr ) //calls operator bool() cout<<"int value is:"<<*ptr <<endl; else cout<<"empty"<<endl; |
Furthermore, the implicit conversion
to bool is required in conditional
declarations such as:
1 2 3 4 | if (shared_ptr<X> px = dynamic_pointer_cast<X>(py)) { //we get here only of px isn't empty } |
Alas, this automatic conversion opens
the gate to unwelcome surprises:
1 2 3 4 5 6 7 8 9 10 11 12 | 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的转换,使只有1个隐式转换,这将防止前面提到的意外行为:pmember->bool,而不是bool->something else。
我最喜欢的(暂时)是这样的陈述缺乏语义A=B=C,A的值基本上不确定。
想想看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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无关的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 "; // #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) <<" "; /* #5 sizeof(Empty) is never 0 */ /* #6 falling off of main without an explicit return means"return 0;" */ } |
您可以使用一些编译器通过命令行开关查看所有预定义的宏。这与GCC和ICC(英特尔的C++编译器)一起工作:
1 2 3 4 5 6 | $ 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 |
对于MSVC,它们在单个位置列出。它们也可以在一个单独的地方为其他人记录,但是通过上面的命令,在应用所有其他命令行开关之后,您可以清楚地看到什么是定义的,什么不是定义的,以及使用了什么值。
比较(排序后):
1 2 3 | $ diff gxx-macros.txt icx-macros.txt $ diff gxx-macros.txt gcc-macros.txt $ diff icx-macros.txt icc-macros.txt |
成员指针和成员指针运算符->*
1 2 3 4 5 6 7 8 9 | #include <stdio.h> struct A { int d; int e() { return d; } }; int main() { A* a = new A(); a->d = 8; printf("%d %d ", a ->* &A::d, (a ->* &A::e)() ); return 0; } |
对于方法(a->*&a::e)()有点像javascript中的函数.call()。
1 2 | var f = A.e f.call(a) |
对于成员来说,这有点像使用[]运算符访问
1 | a['d'] |
向模板添加约束。
用静态铸造模拟重新解释铸造:
1 2 | int var; string *str = reinterpret_cast<string*>(&var); |
上述代码相当于:
1 2 | int var; string *str = static_cast<string*>(static_cast<void*>(&var)); |
模板元编程是。
不是隐藏的特征,而是纯粹的敬畏:
1 | #define private public |
可以将变量引用作为函数的一部分返回。它有一些用途,主要用于生成可怕的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 } |
指针算术。
它实际上是一个C特性,但是我注意到很少有人使用C/C++来真正意识到它甚至存在。我认为C语言的这一特性真正展示了它的发明者的天赋和远见。
为了长话短说,指针算术允许编译器对任何类型的a执行[n]as*(a+n)。作为旁注,由于"+"是可交换的,a[n]当然等同于n[a]。
我认识一个人,他用一个方法同时定义了getter和setter。这样地:
1 2 3 4 5 6 7 8 | class foo { int x; int* GetX(){ return &x; } } |
现在,您可以像往常一样使用它作为getter(好的,几乎是):
1 | int a = *GetX(); |
作为一个设定者:
1 | *GetX() = 17; |