当涉及到“隐藏的特征”这一行问题时,没有 C++ 的爱吗?我想我会把它扔在那里。C++有哪些隐藏的特性?
64 回答
大多数 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++ 是一种多范式语言,因此您会发现的“隐藏”功能(除了您应该不惜一切代价避免的“未定义行为”)是对设施的巧妙使用。
这些工具中的大多数不是该语言的内置功能,而是基于库的功能。
最重要的是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] 的同义词
你没有说有用... :-)
鲜为人知的一件事是联合也可以是模板:
template<typename From, typename To>
union 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...
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
}
绑定到 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
元函数,但是对于非模板情况,它有一个很好的用例:轻松编写声明:
// 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 条件中定义变量,其范围将仅跨越 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()) {
// ...
}
(也处于 for 条件)。但我不太确定这些是否有用:)
防止逗号运算符调用运算符重载
有时您有效地使用了逗号运算符,但您想确保没有用户定义的逗号运算符妨碍您,因为例如您依赖于左侧和右侧之间的序列点,或者想要确保没有任何干扰行动。这是void()
进入游戏的地方:
for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);
忽略我为条件和代码放置的占位符。重要的是void()
,它使编译器强制使用内置逗号运算符。有时,这在实现特征类时也很有用。
构造函数中的数组初始化。例如,在一个类中,如果我们有一个int
as 数组:
class clName
{
clName();
int a[10];
};
我们可以在构造函数中将数组中的所有元素初始化为其默认值(这里数组的所有元素为零),如下所示:
clName::clName() : a()
{
}
哦,我可以想出一个宠物讨厌的清单:
- 如果您打算以多态方式使用析构函数,则需要是虚拟的
- 有时成员是默认初始化的,有时不是
- 局部类不能用作模板参数(使它们不太有用)
- 异常说明符:看起来有用,但不是
- 函数重载隐藏了具有不同签名的基类函数。
- 国际化没有有用的标准化(便携式标准宽字符集,有人吗?我们必须等到 C++0x)
从积极的一面
- 隐藏功能:功能尝试块。不幸的是,我还没有找到它的用途。是的,我知道他们为什么添加它,但是您必须重新抛出一个构造函数,这使得它毫无意义。
- 值得仔细看看 STL 对容器修改后迭代器有效性的保证,它可以让你做一些更好的循环。
- Boost - 这不是什么秘密,但值得使用。
- 返回值优化(不明显,但标准明确允许)
- 函子又名函数对象又名运算符()。这被 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<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);
}
隐藏功能:
- 纯虚函数可以有实现。常见的例子,纯虚析构函数。
如果一个函数抛出了一个未在其异常规范中列出的异常,但该函数
std::bad_exception
在其异常规范中有,则该异常将自动转换为std::bad_exception
并抛出。这样你至少会知道 abad_exception
被抛出了。在这里阅读更多。功能尝试块
在类模板中消除 typedef 歧义的模板关键字。如果成员模板特化的名称出现在
.
、->
或::
运算符之后,并且该名称具有明确限定的模板参数,请在成员模板名称前加上关键字 template。在这里阅读更多。函数参数默认值可以在运行时更改。在这里阅读更多。
A[i]
效果一样好i[A]
可以修改类的临时实例!可以在临时对象上调用非常量成员函数。例如:
struct Bar { void modify() {} } int main (void) { Bar().modify(); /* non-const function invoked on a temporary. */ }
在这里阅读更多。
:
如果在三元 ( ) 运算符表达式的前后存在两种不同的类型?:
,则表达式的结果类型是两者中最通用的类型。例如: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) }
另一个隐藏的功能是您可以调用可以转换为函数指针或引用的类对象。对它们的结果进行重载解析,并完美地转发参数。
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
}
这些被称为“代理调用函数”。
map::operator[]
如果缺少键,则创建条目并返回对默认构造的条目值的引用。所以你可以写:
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
它们限制在文件范围内的使用。
在类模板中定义普通友元函数需要特别注意:
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++ 模板粘贴此部分:完整指南/第 8.4 节
void 函数可以返回 void 值
鲜为人知,但下面的代码很好
void f() { }
void g() { return f(); }
以及以下看起来很奇怪的一个
void f() { return (void)"i'm discarded"; }
了解了这一点,您可以在某些领域发挥优势。一个例子:void
函数不能返回一个值,但你也不能什么都不返回,因为它们可能被实例化为非 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; !
};
将文件读入字符串向量:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
任何编程语言中最有趣的语法之一。
其中三个属于一起,两个是完全不同的东西......
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));
除了第三个和第五个之外,所有SomeType
对象都在堆栈上定义了一个对象并对其进行初始化(u
在前两种情况下,在第四种情况下使用默认构造函数。第三个是声明一个不带参数并返回 a 的函数SomeType
。第五个类似地声明一个函数,它通过SomeType
名为的类型的值获取一个参数u
。
您可以对位域进行模板化。
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
我还没有为此想出任何目的,但这确实让我感到惊讶。
支配规则很有用,但鲜为人知。它说即使在通过基类晶格的非唯一路径中,如果成员属于虚拟基类,则部分隐藏成员的名称查找也是唯一的:
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 名称、静态/非虚拟成员和其他任何东西。我已经看到它用于在元程序中实现可覆盖的特征。
三元条件运算符?:
要求其第二个和第三个操作数具有“令人愉快”的类型(非正式地说)。但是这个要求有一个例外(双关语):第二个或第三个操作数可以是一个 throw 表达式(它具有 type void
),而不管另一个操作数的类型。
换句话说,可以使用?:
运算符编写以下完全有效的 C++ 表达式
i = a > b ? a : throw something();
顺便说一句, throw 表达式实际上是一个表达式(类型void
)而不是语句,这是 C++ 语言的另一个鲜为人知的特性。这意味着,除其他外,以下代码是完全有效的
void foo()
{
return throw something();
}
虽然这样做没有多大意义(也许在一些通用模板代码中这可能会派上用场)。
摆脱前向声明:
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;
我发现这个博客是一个关于 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
{ ... };
template <typename T>
MyAwesomeAbstractClass*
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
!
众多示例中的一个:模板元编程。标准委员会中没有人打算在编译时执行图灵完备的子语言。
模板元编程几乎不是一个隐藏的功能。它甚至在 boost 库中。请参阅MPL。但如果“几乎隐藏”足够好,那么看看boost 库。它包含许多好东西,如果没有强大的库支持,这些好东西是不容易获得的。
一个例子是boost.lambda库,它很有趣,因为 C++ 在当前标准中没有 lambda 函数。
另一个例子是Loki,它“广泛使用 C++ 模板元编程并实现了几个常用工具:typelist、functor、singleton、smart pointer、object factory、visitor 和 multimethods。” [维基百科]
没有隐藏的功能,但是 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 的真正神奇的书“Modern C++ Design ...” .
最后,C++ 很难学,你应该努力补上 ;)
在我看来,只有少数人知道未命名的命名空间:
namespace {
// Classes, functions, and objects here.
}
未命名的命名空间的行为就像它们被替换为:
namespace __unique_name__ { /* empty body */ }
using namespace __unique_name__;
namespace __unique_name__ {
// original namespace body
}
“.. 在翻译单元中所有出现的 [此唯一名称] 都被相同的标识符替换,并且此标识符不同于整个程序中的所有其他标识符。” [C++03, 7.3.1.1/1]
来自C++ 真理。
在同一范围内定义具有相同签名的函数,因此这是合法的:
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";
}
有很多“未定义的行为”。您可以学习如何避免他们阅读好书和阅读标准。
大多数 C++ 开发人员忽略了模板元编程的力量。查看Loki 库。它广泛使用模板元编程(来自维基百科)实现了几个高级工具,如类型列表、函子、单例、智能指针、对象工厂、访问者和多方法。在大多数情况下,您可以将这些视为“隐藏的”c++ 功能。
- 指向类方法的指针
- “类型名”关键字
注意自由函数指针和成员函数指针初始化的区别:
成员函数:
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.
main() 不需要返回值:
int main(){}
是最短的有效 C++ 程序。
map::insert(std::pair(key, value));
如果键值已经存在,则不会覆盖。您可以在定义后立即实例化一个类:(我可能会补充说,由于缺少分号,此功能给我带来了数百个编译错误,而且我从未见过有人在类上使用它)
class MyClass {public: /* code */} myClass;
假设您正在设计一个智能指针类。除了重载运算符 * 和 -> 之外,智能指针类通常定义一个转换运算符为 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;
此外,在条件声明中需要隐式转换为 bool,例如:
if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
//we get here only of px isn't empty
}
唉,这种自动转换打开了不受欢迎的惊喜之门:
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别的。
如果 operator delete() 除了 *void 之外还接受 size 参数,这意味着它将高度地成为一个基类。该大小参数可以检查类型的大小以销毁正确的类型。斯蒂芬杜赫斯特讲述了这一点:
另请注意,我们使用了 operator delete 的两个参数版本,而不是通常的一个参数版本。这个有两个参数的版本是成员 operator delete 的另一个“通常”版本,通常由基类使用,这些基类期望派生类继承其 operator delete 实现。第二个参数将包含要删除的对象的大小——这些信息在实现自定义内存管理时通常很有用。
类和结构类键几乎相同。主要区别在于类默认为成员和基的私有访问,而结构默认为公共访问:
// 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
联合也可以有成员和方法,并且与结构类似,默认为公共访问。
我发现递归模板实例非常酷:
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; }
};
我用它来生成一个包含 10-15 个函数的类,这些函数将指针返回到数组的各个部分,因为我使用的 API 需要一个函数指针来处理每个值。
即通过递归对编译器进行编程以生成一堆函数。非常简单。:)
向模板添加约束。
您可以使用某些编译器通过命令行开关查看所有预定义的宏。这适用于 gcc 和 icc(英特尔的 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
对于 MSVC,它们被列在一个地方。它们也可以记录在一个地方供其他人使用,但是在应用所有其他命令行开关后,通过上述命令,您可以清楚地看到定义和未定义的内容以及使用的值。
比较(排序后):
$ diff gxx-macros.txt icx-macros.txt
$ diff gxx-macros.txt gcc-macros.txt
$ diff icx-macros.txt icc-macros.txt
My favorite (for the time being) is the lack of sematics in a statement like A=B=C. What the value of A is basically undetermined.
Think of this:
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;
}
now A might be of a type inaccessible to any other than objects of type clB and have a value that's unrelated to 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']
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;" */
}
使用静态演员模拟重新解释演员表:
int var;
string *str = reinterpret_cast<string*>(&var);
上面的代码等价于:
int var;
string *str = static_cast<string*>(static_cast<void*>(&var));
Template metaprogramming is.
You can return a variable reference as part of a function. It has some uses, mostly for producing horrible code:
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 执行 a[n] 作为 *(a+n)。附带说明一下,因为“+”是可交换的,所以 a[n] 当然等价于 n[a]。
实际上不是隐藏功能,而是纯粹的令人敬畏:
#define private public
我认识一个只用一种方法同时定义 getter 和 setter 的人。像这样:
class foo
{
int x;
int* GetX(){
return &x;
}
}
您现在可以像往常一样使用它作为吸气剂(嗯,几乎):
int a = *GetX();
作为二传手:
*GetX() = 17;