C ++的隐藏功能?[关闭]


114

当涉及到“隐藏特征”问题时,没有C ++喜欢吗?想通了,我会把它扔在那里。C ++的一些隐藏功能是什么?


@Devtron-我已经看到了一些很棒的错误(即意外行为)作为功能出售。实际上,如今游戏行业实际上试图做到这一点,并称其为“新兴游戏玩法”(也可以从Psi-Ops中查看“ TK Surfing”,纯粹是一个错误,然后他们将其保持原样,并且它是其中之一)。游戏(IMHO)的最佳功能)
格兰特·彼得斯

5
@Laith J:很少有人从头到尾阅读786页的ISO C ++标准-但我想您已经读了,并且您保留了所有这些,对吧?
j_random_hacker 2010年

2
@ Laith,@ j_random:在stackoverflow.com/questions/1/you-have-been-link-rolled上看到我的问题“什么是程序员的笑话,我怎么识别它,以及适当的响应是什么” 。

Answers:


308

大多数C ++程序员都熟悉三元运算符:

x = (y < 0) ? 10 : 20;

但是,他们没有意识到它可以用作左值:

(a == 0 ? a : b) = 1;

这是简写

if (a == 0)
    a = 1;
else
    b = 1;

谨慎使用:-)


11
很有意思。我可以看到,但是却制作了一些不可读的代码。
杰森·贝克

112
kes (a == 0?a:b)=(y <0?10:20);
贾斯珀·贝克斯

52
(b?trueCount:falseCount)++
Pavel Radzivilovsky 2010年

12
说不上来,如果它的GCC具体,但我很惊讶地发现,这也工作:(value ? function1 : function2)()
克里斯·伯特·布朗

3
@Chris Burt-Brown:不,如果它们具有相同的类型(即没有默认参数)function1并且function2被隐式转换为函数指针,并且结果被隐式转换回去,那么该方法应该可以在任何地方使用。
MSalters 2010年

238

您可以将URI正确地放入C ++源代码中。例如:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
但是我怀疑每个功能只有一个吗?:)
康斯坦丁

51
@jpoh:http后跟冒号将成为一个“标签”,您稍后将在goto语句中使用该标签。您会从编译器收到该警告,因为在上例中的任何goto语句中均未使用该警告。
utku_karatas

9
您可以添加多个协议,只要它们具有不同的协议即可!ftp.microsoft.com gopher://aerv.nl/1等等...
Daniel Earwicker 2009年

4
@Pavel:标识符后跟冒号是标签(与gotoC ++一起使用)。在两个斜杠后面的所有内容均为注释。因此,有http://stackoverflow.comhttp是一个标签(理论上你可以写goto http;),并且//stackoverflow.com仅仅是一个结束行注释。两者都是合法的C ++,因此可以编译该构造。当然,它并没有做任何模糊有用的事情。
David Thornley,2010年

8
不幸的是goto http;,实际上并没有遵循该URL。:(
Yakov Galka 2011年

140

指针算术。

由于可能会引入错误,因此C ++程序员更喜欢避免使用指针。

我见过最酷的C ++吗?模拟文字。


11
我们避免由于错误而指向指针?指针基本上就是动态C ++编码所涉及的一切!
Nick Bedford 2010年

1
模拟文字非常适合混淆的C ++竞赛条目,尤其是ASCII艺术类型。
Synetech

119

我同意那里的大多数文章: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


+1非常彻底的答案。由于显而易见的原因,它是不完整的(否则,将不再有“隐藏的功能”!):p在答案末尾的第一点,您提到了类接口的成员。您的意思是“ ..既是其成员函数又是朋友的非成员函数”吗?
威廉姆特”


您提到的1必须是koenig查找,对吗?
Özgür的

1
@wilhelmtell:否否否... :-p ...我的意思是“其成员函数和NON-FRIEND非成员函数”...。Koenig的Lookup将确保这些函数比其他函数早被考虑”外部”搜索符号
paercebal

7
很棒的职位,尤其是最后一部分的+1,却很少有人意识到。我可能还会将Boost库添加为“隐藏功能”。我几乎认为它是C ++应该具有的标准库。;)
jalf

118

我认为在某种程度上是隐藏的一种语言功能是名称空间别名,因为我在整个学校期间都从未听说过它。直到我在boost文档中遇到它的示例时,才引起我注意。当然,现在我知道了,您可以在任何标准C ++参考中找到它。

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );

1
如果您不想使用它,我想这很有用using
Siqi Lin

4
它是作为一种方法来实现之间切换也非常有用,是否选择比如线程安全与非线程安全的,或版本相对于1 2
托尼德尔罗伊

3
如果您正在处理具有较大名称空间层次结构的大型项目,并且不希望标头引起名称空间污染(并且希望变量声明易于理解),则此功能特别有用。
Brandon Bohrer

102

不仅可以在for循环的init部分声明变量,还可以在类和函数中声明变量。

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

这允许使用不同类型的多个变量。


31
很高兴知道您可以做到,但是就我个人而言,我真的会尽量避免这样做。主要是因为它很难阅读。
Zoomulator

2
实际上,在这种情况下可以使用对:for(std :: pair <int,float> loop = std :: make_pair(1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@Valentin很好,那么我建议您尝试对VS2008进行错误报告,而不要拒绝隐藏功能。显然这是编译器的错误。
Johannes Schaub-litb

2
嗯,它在msvc10中也不起作用。多么伤心:(
avakar 2010年

2
@avakar实际上,gcc在v4.6中引入了一个使其也被拒绝的错误:)请参阅gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Johannes Schaub-litb 2010年

77

数组运算符是关联的。

A [8]是*(A + 8)的同义词。由于加法是关联的,因此可以改写为*(8 + A),这是..... 8 [A]的同义词

你没说有用... :-)


15
实际上,使用此技巧时,您应该真正注意所使用的类型。A [8]实际上是第8个A,而8 [A]是从地址8开始的Ath整数。如果A是一个字节,则说明存在错误。
文森特·罗伯特

38
你的意思是“可交换的”,你说的是“联想的”?
DarenW,

28
文森特,你错了。的类型A根本不重要。例如,如果Achar*,则代码仍然有效。
Konrad Rudolph

11
注意,A必须是一个指针,而不是一个类重载operator []。
DavidRodríguez-dribeas,2009年

15
Vincent,在这种情况下,必须有一个整数类型和一个指针类型,并且C和C ++都不在乎哪个优先。
David Thornley,2009年

73

鲜为人知的一件事是,联合也可以是模板:

template<typename From, typename To>
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

他们也可以具有构造函数和成员函数。与继承(包括虚拟函数)无关。


有趣!那么,您必须初始化所有成员吗?它是否遵循通常的结构顺序,这意味着最后一个成员将被初始化为“在”先前成员的“顶部”?
j_random_hacker

j_random_hacker哦,那是胡说八道。接得好。我写它,因为它将是一个结构。等待我将其修复
Johannes Schaub-litb

这不是调用未定义的行为吗?
格雷格·培根

7
@gbacon,是的,如果FromTo被相应地设置和使用,它会调用未定义的行为。不过,这样的联合可以用于定义的行为(使用To无符号字符数组或与共享初始序列的结构From)。即使您以未定义的方式使用它,它对于底层工作也可能仍然有用。无论如何,这只是联合模板的一个示例-模板化联合可能还有其他用途。
Johannes Schaub-litb 2010年

3
小心构造函数。请注意,您只需要构造第一个元素,并且仅在C ++ 0x中允许。按照当前的标准,您必须坚持一些可构造的类型。而且没有破坏者。
Potatoswatter 2010年

72

C ++是一个标准,不应有任何隐藏的功能...

C ++是一种多范式语言,您可以赌上最后一笔钱是隐藏功能。众多示例之一:模板元编程。标准委员会中没有人希望在编译时执行图灵完备的子语言。


65

在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
}

61

绑定到const引用的临时文件的生命周期是很少有人知道的。或者至少这是大多数人都不知道的我最喜欢的C ++知识。

const MyClass& x = MyClass(); // temporary exists as long as x is in scope

3
你能详细说明吗?正如您只是在戏弄;)
Joseph Garvin

8
ScopeGuard(ddj.com/cpp/184403758)是利用此功能的一个很好的例子。
MSN

2
我和约瑟夫·加文在一起。请赐教。
彼得·莫滕森

我只是在评论中做了。此外,使用const引用参数是自然的结果。
MSN


52

函数范围内的try-catch块是一个不经常使用的好功能:

int Function()
try
{
   // do something here
   return 42;
}
catch(...)
{
   return -1;
}

主要用法是将异常转换为其他异常类并重新抛出,或者在异常和基于返回的错误代码处理之间进行转换。


我认为您无法return从Function Try的catch块中重新抛出。
康斯坦丁

我只是尝试编译以上内容,但未给出任何警告。我认为上述示例有效。
生动的

7
return只禁止构造函数使用。构造函数的功能try块将捕获初始化基数和成员的错误(唯一的情况是,功能try块所做的事情与仅在函数内部进行尝试不同);不重新抛出将导致对象不完整。
puetzk

是。这非常有用。我编写了宏BEGIN_COM_METHOD和END_COM_METHOD来捕获异常并返回HRESULTS,以使异常不会从实现COM接口的类中泄漏出去。运行良好。
Scott Langham

3
正如@puetzk指出的那样,这是处理构造函数的初始化程序列表中任何事物(例如基类的构造函数或数据成员的构造函数)引发的异常的唯一方法。
anton.burger 2010年

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; };

有趣,但最初我实际上在阅读其中一些定义时遇到了更多麻烦。解决C ++声明由内而外的另一种方法是编写一些模板类型别名: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;
bames53 2012年

42

一个相当隐蔽的功能是您可以在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条件)。但是我不太确定这些是否有用:)


整齐!我不知道您能做到...在编写带有错误返回值的代码时,它将(并将)节省一些麻烦。有没有办法以这种形式仍然有条件而不只是!= 0?if((int r = func())<0)似乎不起作用...
puetzk

普埃茨克,不,没有。但很高兴您喜欢它:)
Johannes Schaub-litb

4
@Frerich,这在C代码中根本不可能。我认为您正在考虑if((a = f()) == b) ...,但是此答案实际上在条件中声明了一个变量。
Johannes Schaub-litb

1
@Angry这是非常不同的,因为变量声明会立即测试其布尔值。也有一个到for循环的映射,看起来for(...; int i = foo(); ) ...;只要它i是true,它就会通过主体,并再次对其进行初始化。您显示的循环只是演示一个变量声明,而不是同时充当条件的变量声明:)
Johannes Schaub-litb 2010年

5
很好,除非您没有提到此功能的预期用途是动态指针强制转换,我相信。
mmocny 2011年

29

防止逗号运算符调用运算符重载

有时您可以有效地使用逗号运算符,但是您要确保没有用户定义的逗号运算符进入该方式,因为例如您依赖于左侧和右侧之间的序列点,或者想要确保没有任何东西干扰所需的逗号行动。这是void()游戏的地方:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

忽略我输入条件和代码的占位符。重要的是void(),这使编译器可以强制使用内置的逗号运算符。有时也可以在实现特征类时使用。


我只是用它来结束我的过分表达忽略器。:)
GManNickG 2011年

28

构造函数中的数组初始化。例如在一个类中,如果我们有一个intas 数组:

class clName
{
  clName();
  int a[10];
};

我们可以在构造函数中将数组中的所有元素初始化为其默认值(此处,数组中的所有元素均为零):

clName::clName() : a()
{
}

6
您可以在任何地方的任何阵列上执行此操作。
土豆泥

@Potatoswatter:由于最令人讨厌的解析,它比看起来更难。我想不出其他地方是可以做到的,但也许一个返回值
鸣叫鸭

如果数组的类型是类类型,那么这不是必需的吗?
Thomas Eding

27

噢,我可以拿出一份宠物恨清单:

  • 如果您打算多态使用析构函数,则它们必须是虚拟的
  • 有时成员默认情况下被初始化,有时不是
  • 本地分类不能用作模板参数(使它们不太有用)
  • 异常说明符:看起来很有用,但没有用
  • 函数重载隐藏具有不同签名的基类函数。
  • 没有关于国际化的有用标准化(便携式标准宽字符集,有人吗?我们将不得不等到C ++ 0x)

从积极的一面

  • 隐藏功能:功能尝试块。不幸的是我还没有找到用处。是的,我知道他们为什么添加它,但是您必须重新添加一个使其毫无意义的构造函数。
  • 值得仔细研究一下容器修改后有关迭代器有效性的STL保证,这可以使您进行一些更好的循环。
  • 增强-几乎不是秘密,但值得使用。
  • 返回值优化(不明显,但标准明确允许)
  • 函子又名函数对象又名operator()。STL广泛使用它。这并不是一个秘密,但它是操作员重载和模板的一个令人讨厌的副作用。

16
讨厌:没有为C ++应用程序定义的ABI,不同于每个人都使用的C语言,因为每种语言都可以保证调用C函数,没有人可以为C ++做同样的事情。
gbjbaanb

8
仅当您打算多态销毁时,析构函数才需要是虚拟的,这与第一点略有不同。
DavidRodríguez-dribeas,2009年

2
使用C ++ 0x时,可以将本地类型用作模板参数。
tstenner

1
在C ++ 0x中,如果对象具有任何虚函数(即vtable),则析构函数将为虚函数。
Macke

不要忘记NRVO,当然,只要不改变程序输出
jk,

26

您可以访问任何类的受保护数据和函数成员,而没有未定义的行为,并且可以使用预期的语义。继续阅读以了解操作方法。另请阅读缺陷报告有关此问题。

通常,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);
}


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
}

这些被称为“代理调用函数”。


1
当您说对它们的结果进行重载解析时,您是说实际上将其转换为两个函子,然后重载解析吗?我尝试在操作符Func1 *()和操作符Func2 *()中打印某些内容,但是当它找出要调用的转换操作符时,似乎选择了正确的内容。
导航器

3
@navigator,是的,它在概念上会转换为两者,然后选择最佳的。它不需要实际调用它们,因为它从结果类型知道它们将产生什么。当事实证明最终选择了什么时,才完成实际的调用。
Johannes Schaub-litb

26

隐藏功能:

  1. 纯虚函数可以实现。常见示例是纯虚拟析构函数。
  2. 如果一个函数抛出一个异常,但该异常未在其异常规范中列出,但是该函数std::bad_exception在其异常规范中具有该异常,则该异常将std::bad_exception被自动转换并引发。这样,您至少会知道bad_exception抛出了a 。在这里阅读更多。

  3. 功能尝试块

  4. template关键字用于消除类模板中的typedef的歧义。如果成员模板特的名字出现后.->::运营商,这个名字有明确限定的模板参数,前缀的成员模板名称以关键字template。在这里阅读更多。

  5. 功能参数的默认值可以在运行时更改。在这里阅读更多。

  6. A[i] 一样好 i[A]

  7. 一个类的临时实例可以修改!可以在临时对象上调用非常量成员函数。例如:

    struct Bar {
      void modify() {}
    }
    int main (void) {
      Bar().modify();   /* non-const function invoked on a temporary. */
    }

    在这里阅读更多。

  8. 如果:在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)
    }

P爸爸:A [i] == *(A + i)== *(i + A)== i [A]
abelenky

我得到了换向,只是这意味着[]没有其自身的语义值,并且仅等效于宏样式替换,其中“ x [y]”替换为“(*((x)+(y )))”。完全不符合我的期望。我不知道为什么要这样定义。
P Daddy

与C的向后兼容性
jmucchiello,2009年

2
关于您的第一点:在一个特殊的情况下,您必须实现一个纯虚函数:纯虚析构函数。
弗里希·拉贝

24

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 ++程序员不知道这一点。


11
而在另一端,你不能一个const地图上使用操作符[]
dribeas大卫-罗德里格斯

2
对于尼克+1,如果他们不了解,人们可能会发疯.find()
LiraNuna

或“ const map::operator[]生成错误消息”
某人

2
这不是语言的功能,而是标准模板库的功能。这也很明显,因为operator []返回有效的引用。
Ramon Zarazua B.

2
我不得不在C#中使用地图一段时间,而地图却无法以这种方式运行,以便意识到这是一个功能。我以为我比使用它时更烦恼,但似乎我错了。我在C#中找不到它。
2010年

20

将函数或变量放在无名的命名空间中,不赞成使用static来将它们限制在文件范围内。


“不赞成”是一个很强的名词……
Potatoswatter 2010年

@Potato:我知道是老评论,但是标准确实说不建议在命名空间范围内使用静态,而优先使用未命名的命名空间。
GManNickG 2010年

@GMan:没有问题,我不认为SO页真的“死了”。仅就故事的双方而言,static全球范围内的任何情况都不会被弃用。(供参考:C ++ 03§D.2)
Potatoswatter 2010年

嗯,仔细阅读,“在全局名称空间中声明的名称具有全局名称空间范围(也称为全局范围)。” 这真的意味着吗?
Potatoswatter 2010年

@土豆:是的。:) static使用只能在类类型或函数中使用。
GManNickG 2010年

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节


18

void函数可以返回void值

鲜为人知,但以下代码很好

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; !
};

17

将文件读入字符串向量:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
或:vector <string> V((istream_iterator <string>(cin)),istream_iterator <string>);
UncleBens

5
您的意思是vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());–在第二个参数后缺少括号
knittl 2010年

1
这实际上不是隐藏的C ++功能。更多STL功能。STL!=一种语言
Nick Bedford 2010年


14

任何编程语言中最有趣的语法之一。

其中三件事是在一起的,两件事完全不同...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

除了第三个和第五个以外的所有SomeType对象在堆栈上定义一个对象并对其进行初始化(u在前两种情况下使用,在第四个情况下使用默认的构造函数。第三个是声明一个不带参数并返回a的函数SomeType。第五个类似地声明一种函数,该函数通过一个SomeType名为类型的值接受一个参数u


第一和第二之间有什么区别吗?不过,我知道它们都是初始化。
Özgür的

推特:我不这么认为。两者最终都将调用复制构造函数,即使第一个LOOKS像赋值运算符一样,它实际上也是复制构造函数。
abelenky

1
如果u是不同于SomeType的类型,则第一个将首先调用转换构造函数,然后再调用复制构造函数,而第二个将仅调用转换构造函数。

3
第一个是构造函数的隐式调用,第二个是显式调用。查看以下代码,以了解它们之间的区别:#include <iostream>类sss {public:显式sss(int){std :: cout <<“ int” << std :: endl; }; sss(double){std :: cout <<“ double” << std :: endl; }; }; int main(){sss ddd(7); //调用int构造函数sss xxx = 7; //调用double构造函数返回0; }
Kirill V. Lyadvinsky 09年

True-如果声明构造函数为显式,则第一行将不起作用。

12

摆脱前向声明:

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;

2
FStruct s = {};更短。
康斯坦丁

在最后一个示例中,使用以下命令会更简单:b(); 浮点c = 1.0f;
Zifre

2
此语法为“ float c =(a(),b(),1.0f);” 有助于强调分配操作(“ c”的辅助)。分配操作在编程中很重要,因为它们不太可能成为不推荐使用的IMO。不知道为什么,这可能与函数编程有关,在函数编程中,每帧都要重新分配程序状态。PS。不,“ int d =(11,22,1.0f)”将等于“ 1”。一分钟前使用VS2008进行了测试。
AareP

2
+1您不应该打电话 main吗?我建议global().main();
不要

1
我怀疑分配引用是否可移植。我喜欢该结构免除向前声明。
Thomas Eding

12

三元条件运算符?:要求其第二和第三操作数具有“令人满意的”类型(非正式地说)。但是此要求有一个例外(双关语意味):第二个或第三个操作数可以是throw表达式(其类型为void),而与另一个操作数的类型无关。

换句话说,可以使用?:运算符编写以下正确有效的C ++表达式

i = a > b ? a : throw something();

顺便说一句,throw表达式实际上是表达式(类型void)而不是语句,这是C ++语言的另一个鲜为人知的功能。这意味着,除其他外,以下代码是完全有效的

void foo()
{
  return throw something();
}

尽管这样做没有什么意义(也许在某些通用模板代码中可能会派上用场)。


对于它的价值,尼尔对此有一个疑问:stackoverflow.com/questions/1212978/…,只是为了获得更多信息。
GManNickG 2010年

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名称,静态/非虚拟成员以及其他任何东西。我已经看到它曾经在元程序中实现可重写的特征。


1
整齐。您包含struct C在示例中的任何特殊原因...?干杯。
托尼·德罗伊
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.