当涉及到“问题线”的“隐藏特征”时,没有C ++的爱吗? 想我会把它扔出去。 C ++的一些隐藏功能是什么?
C ++的隐藏功能? [关闭]
Hidden Features of C++? [closed]
===============>>#1 票数:308
大多数C ++程序员都熟悉三元运算符:
x = (y < 0) ? 10 : 20;
但是,他们没有意识到它可以用作左值:
(a == 0 ? a : b) = 1;
这是简写
if (a == 0)
a = 1;
else
b = 1;
谨慎使用:-)
===============>>#2 票数:238
您可以将URI放入C ++源代码而不会出错。 例如:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
===============>>#4 票数:119
我同意那里的大多数帖子: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 )
===============>>#5 票数:118
我认为有些隐藏的一种语言功能,因为我在学校的整个过程中从未听说过它,是名称空间别名。 直到我在boost文档中遇到它的例子之后才引起我的注意。 当然,现在我了解它,你可以在任何标准的C ++参考中找到它。
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
===============>>#6 票数:102
变量不仅可以在for
循环的init部分中声明,还可以在类和函数中声明。
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
这允许多个不同类型的变量。
===============>>#7 票数:77
数组运算符是关联的。
A [8]是*(A + 8)的同义词。 由于加法是关联的,可以改写为*(8 + A),这是..... 8 [A]的同义词
你没说有用...... :-)
===============>>#8 票数:73
有一点鲜为人知的是,工会也可以是模板:
template<typename From, typename To>
union union_cast {
From from;
To to;
union_cast(From from)
:from(from) { }
To getTo() const { return to; }
};
他们也可以拥有构造函数和成员函数。 没有任何与继承(包括虚函数)有关的事情。
===============>>#9 票数:72
C ++是一个标准,不应该有任何隐藏的功能......
C ++是一种多范式语言,你可以打赌你的最后一笔钱是隐藏的功能。 许多例子中的一个例子: 模板元编程 。 标准委员会中没有人打算在编译时执行图灵完整的子语言。
===============>>#10 票数:66
另一个在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
}
===============>>#11 票数:61
与const引用相关的临时工的生命期是很少有人知道的。 或者至少它是我最喜欢的C ++知识,大多数人都不知道。
const MyClass& x = MyClass(); // temporary exists as long as x is in scope
===============>>#12 票数:52
一个不常用的好功能是功能范围的try-catch块:
int Function()
try
{
// do something here
return 42;
}
catch(...)
{
return -1;
}
主要用法是将异常转换为其他异常类和重新抛出,或者在异常和基于返回的错误代码处理之间进行转换。
===============>>#13 票数:44
许多人都知道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; };
===============>>#14 票数:43
一个非常隐蔽的功能是您可以在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()) {
// ...
}
(也适用于条件)。 但我不太确定这些是否都有用:)
===============>>#15 票数:29
防止逗号运算符调用运算符重载
有时您可以有效地使用逗号运算符,但是您希望确保没有用户定义的逗号运算符妨碍,因为例如您依赖于左侧和右侧之间的序列点,或者希望确保没有任何干扰所需的行动。 这是void()
进入游戏的地方:
for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);
忽略我为条件和代码提出的占位符。 重要的是void()
,它使编译器强制使用内置的逗号运算符。 这有时在实现traits类时很有用。
===============>>#16 票数:28
构造函数中的数组初始化。 例如,在类中,如果我们有一个int
数组:
class clName
{
clName();
int a[10];
};
我们可以在构造函数中将数组中的所有元素初始化为其默认值(此处数组的所有元素为零):
clName::clName() : a()
{
}
===============>>#17 票数:27
您可以访问任何类的受保护数据和函数成员,没有未定义的行为,并具有预期的语义。 继续阅读,看看如何。 另请阅读有关此问题的缺陷报告 。
通常,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声明会更容易,这会使成员名称为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);
}
===============>>#18 票数:27
哦,我可以提出一个宠物仇恨列表:
- 如果您打算使用多态,则析构函数必须是虚拟的
- 有时会默认初始化成员,有时则不会
- 本地clases不能用作模板参数(使它们不那么有用)
- 异常说明符:看起来很有用,但不是
- 函数重载隐藏具有不同签名的基类函数。
- 国际化没有有用的标准化(便携式标准宽字符集,任何人?我们必须等到C ++ 0x)
从积极的一面
- 隐藏功能:功能尝试块。 不幸的是我没有找到它的用途。 是的我知道为什么他们添加它,但你必须重新抛出一个构造函数,这使它毫无意义。
- 在容器修改之后,值得仔细查看STL保证迭代器有效性,这可以让你做一些稍好的循环。
- 提升 - 这不是什么秘密,但它值得使用。
- 返回值优化(不明显,但标准特别允许)
- Functors aka function objects aka operator()。 这被STL广泛使用。 不是真正的秘密,但是操作符重载和模板的一个漂亮的副作用。
===============>>#19 票数:26
隐藏功能:
- 纯虚函数可以实现。 常见的例子,纯虚拟析构函数。
如果函数抛出未在其异常规范中列出的异常,但该函数在其异常规范中具有
std::bad_exception
,则异常将转换为std::bad_exception
并自动抛出。 这样你至少会知道抛出了bad_exception
。 在这里阅读更多。功能尝试块
模板关键字在类模板中消除typedef的歧义。 如果成员模板专业化的名称出现在
.
,->
,或::
运算符,并且该名称具有显式限定的模板参数,使用关键字模板作为成员模板名称的前缀。 在这里阅读更多。函数参数默认值可以在运行时更改。 在这里阅读更多。
A[i]
和i[A]
一样好i[A]
可以修改类的临时实例! 可以在临时对象上调用非const成员函数。 例如:
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) }
===============>>#20 票数:26
另一个隐藏的功能是您可以调用可以转换为函数指针或引用的类对象。 对它们的结果进行重载分辨率,并完全转发参数。
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
}
这些被称为“代理呼叫功能”。
===============>>#21 票数:24
map::operator[]
如果缺少key则创建条目,并返回对default-construct条目值的引用。 所以你可以写:
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 ++程序员不知道这一点。
===============>>#22 票数:20
将函数或变量放在无名称命名空间中不赞成使用static
来将它们限制为文件范围。
===============>>#23 票数:19
在类模板中定义普通的朋友函数需要特别注意:
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节粘贴了这一部分
===============>>#24 票数:18
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; !
};
===============>>#25 票数:17
将文件读入字符串向量:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
===============>>#26 票数:14
您可以模板位域。
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
我还没有为此提出任何目的,但确实让我感到惊讶。
===============>>#27 票数:14
任何编程语言中最有趣的语法之一。
这些东西中的三个属于一起,两个是完全不同的东西......
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));
除了第三个和第五个SomeType
对象都在堆栈上定义一个SomeType
对象并初始化它(在前两种情况下使用u
,在第四种情况下使用默认构造函数。第三种是声明一个不带参数的函数并返回SomeType
。类似地声明一个函数,它通过名为u
SomeType
类型的值获取一个参数。
===============>>#28 票数:12
摆脱前瞻性声明:
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;
===============>>#29 票数:12
三元条件运算符?:
要求其第二和第三操作数具有“令人愉快”的类型(非正式地说)。 但是这个要求有一个例外(双关语):第二个或第三个操作数可以是一个throw表达式(类型为void
),而不管其他操作数的类型。
换句话说,可以使用?:
运算符编写以下有效的C ++表达式
i = a > b ? a : throw something();
BTW,throw表达式实际上是一个表达式 (类型为void
)而不是语句这一事实是C ++语言的另一个鲜为人知的特性。 这意味着,以下代码完全有效
void foo()
{
return throw something();
}
虽然这样做没有多大意义(可能在一些通用的模板代码中,这可能会派上用场)。
===============>>#30 票数:12
优势规则很有用,但鲜为人知。 它表示即使在通过基类网格的非唯一路径中,如果成员属于虚拟基类,则部分隐藏成员的名称查找也是唯一的:
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名称,静态/非虚拟成员等。 我已经看到它曾用于在元程序中实现可重写的特性。