当涉及到“隐藏特征”问题时,没有C ++喜欢吗?想通了,我会把它扔在那里。C ++的一些隐藏功能是什么?
当涉及到“隐藏特征”问题时,没有C ++喜欢吗?想通了,我会把它扔在那里。C ++的一些隐藏功能是什么?
Answers:
大多数C ++程序员都熟悉三元运算符:
x = (y < 0) ? 10 : 20;
但是,他们没有意识到它可以用作左值:
(a == 0 ? a : b) = 1;
这是简写
if (a == 0)
a = 1;
else
b = 1;
谨慎使用:-)
(value ? function1 : function2)()
。
function1
并且function2
被隐式转换为函数指针,并且结果被隐式转换回去,那么该方法应该可以在任何地方使用。
您可以将URI正确地放入C ++源代码中。例如:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
goto
C ++一起使用)。在两个斜杠后面的所有内容均为注释。因此,有http://stackoverflow.com
,http
是一个标签(理论上你可以写goto http;
),并且//stackoverflow.com
仅仅是一个结束行注释。两者都是合法的C ++,因此可以编译该构造。当然,它并没有做任何模糊有用的事情。
goto http;
,实际上并没有遵循该URL。:(
我同意那里的大多数文章:C ++是一种多范式语言,因此,您会发现“隐藏”功能(除了应不惜一切代价避免的“未定义行为”)是对设施的巧妙使用。
这些功能大多数都不是语言的内置功能,而是基于库的功能。
最重要的是RAII,多年来,它经常被来自C世界的C ++开发人员所忽略。运算符重载通常是一种容易被误解的功能,它可以启用类似数组的行为(下标运算符),类似指针的操作(智能指针)和类似内置的操作(乘法矩阵)。
异常的使用通常很困难,但是通过一些工作,可以通过异常安全性规范生成真正健壮的代码(包括不会失败的代码,或者具有类似提交功能的代码,这些代码将会成功,或者恢复为旧版本)。其原始状态)。
C ++最著名的“隐藏”功能是模板元编程,因为它使您能够在编译时而不是运行时部分(或全部)执行程序。但是,这很困难,并且在尝试模板之前必须对模板有扎实的了解。
其他人利用多重范式在C ++的祖先(即C)之外产生“编程方式”。
通过使用函子,您可以模拟具有附加类型安全性和状态状态的函数。使用命令模式,可以延迟代码执行。可以使用C ++轻松,有效地实现大多数其他设计模式,以产生替代的编码样式,而这些样式不应包含在“官方C ++范例”列表中。
通过使用模板,您可以生成适用于大多数类型的代码,其中包括您一开始没有想到的代码。您也可以增加类型安全性(例如自动类型安全的malloc / realloc / free)。C ++对象功能确实非常强大(因此,如果不小心使用它会很危险),但是即使动态多态性在C ++中也具有其静态版本:CRTP。
我发现,斯科特·迈耶斯(Scott Meyers)的大多数“ 有效C ++ ”型书籍或赫伯·萨特(Herb Sutter)的“ 非凡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 );
using
。
不仅可以在for
循环的init部分声明变量,还可以在类和函数中声明变量。
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
这允许使用不同类型的多个变量。
数组运算符是关联的。
A [8]是*(A + 8)的同义词。由于加法是关联的,因此可以改写为*(8 + A),这是..... 8 [A]的同义词
你没说有用... :-)
A
根本不重要。例如,如果A
为char*
,则代码仍然有效。
鲜为人知的一件事是,联合也可以是模板:
template<typename From, typename To>
union union_cast {
From from;
To to;
union_cast(From from)
:from(from) { }
To getTo() const { return to; }
};
他们也可以具有构造函数和成员函数。与继承(包括虚拟函数)无关。
From
和To
被相应地设置和使用,它会调用未定义的行为。不过,这样的联合可以用于定义的行为(使用To
无符号字符数组或与共享初始序列的结构From
)。即使您以未定义的方式使用它,它对于底层工作也可能仍然有用。无论如何,这只是联合模板的一个示例-模板化联合可能还有其他用途。
在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;
}
主要用法是将异常转换为其他异常类并重新抛出,或者在异常和基于返回的错误代码处理之间进行转换。
return
从Function Try的catch块中重新抛出。
许多人都知道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; };
template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;
-> pointer<function<void,int>> f(pointer<function<void,void>>);
or pointer<void(int)> f(pointer<void()>);
orfunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
一个相当隐蔽的功能是您可以在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(int value = getIt()) {
// ...
}
并在while循环中:
while(SomeThing t = getSomeThing()) {
// ...
}
(也处于for条件)。但是我不太确定这些是否有用:)
if((a = f()) == b) ...
,但是此答案实际上在条件中声明了一个变量。
for(...; int i = foo(); ) ...;
只要它i
是true,它就会通过主体,并再次对其进行初始化。您显示的循环只是演示一个变量声明,而不是同时充当条件的变量声明:)
有时您可以有效地使用逗号运算符,但是您要确保没有用户定义的逗号运算符进入该方式,因为例如您依赖于左侧和右侧之间的序列点,或者想要确保没有任何东西干扰所需的逗号行动。这是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 ++禁止您访问类对象的非静态受保护成员,即使该类是您的基类也是如此。
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);
}
另一个隐藏的功能是,您可以调用可以转换为函数指针或引用的类对象。对它们的结果进行重载解析,并完美地转发参数。
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
}
这些被称为“代理调用函数”。
隐藏功能:
如果一个函数抛出一个异常,但该异常未在其异常规范中列出,但是该函数std::bad_exception
在其异常规范中具有该异常,则该异常将std::bad_exception
被自动转换并引发。这样,您至少会知道bad_exception
抛出了a 。在这里阅读更多。
功能尝试块
template关键字用于消除类模板中的typedef的歧义。如果成员模板特的名字出现后.
,->
或::
运营商,这个名字有明确限定的模板参数,前缀的成员模板名称以关键字template。在这里阅读更多。
功能参数的默认值可以在运行时更改。在这里阅读更多。
A[i]
一样好 i[A]
一个类的临时实例可以修改!可以在临时对象上调用非常量成员函数。例如:
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)
}
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 ++程序员不知道这一点。
.find()
。
const map::operator[]
生成错误消息”
将函数或变量放在无名的命名空间中,不赞成使用static
来将它们限制在文件范围内。
static
全球范围内的任何情况都不会被弃用。(供参考:C ++ 03§D.2)
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>*)
鲜为人知,但以下代码很好
void f() { }
void g() { return f(); }
还有以下怪异的看一个
void f() { return (void)"i'm discarded"; }
知道了这一点,您可以在某些方面加以利用。一个例子: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));
vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());
–在第二个参数后缺少括号
您可以模板化位域。
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
我还没有想出任何目的,但是确实令我惊讶。
任何编程语言中最有趣的语法之一。
其中三件事是在一起的,两件事完全不同...
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));
除了第三个和第五个以外的所有SomeType
对象在堆栈上定义一个对象并对其进行初始化(u
在前两种情况下使用,在第四个情况下使用默认的构造函数。第三个是声明一个不带参数并返回a的函数SomeType
。第五个类似地声明一种函数,该函数通过一个SomeType
名为类型的值接受一个参数u
。
摆脱前向声明:
struct global
{
void main()
{
a = 1;
b();
}
int a;
void b(){}
}
singleton;
用?:运算符编写开关语句:
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;
FStruct s = {};
更短。
main
吗?我建议global().main();
三元条件运算符?:
要求其第二和第三操作数具有“令人满意的”类型(非正式地说)。但是此要求有一个例外(双关语意味):第二个或第三个操作数可以是throw表达式(其类型为void
),而与另一个操作数的类型无关。
换句话说,可以使用?:
运算符编写以下正确有效的C ++表达式
i = a > b ? a : throw something();
顺便说一句,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名称,静态/非虚拟成员以及其他任何东西。我已经看到它曾经在元程序中实现可重写的特征。
struct C
在示例中的任何特殊原因...?干杯。