C ++的隐藏功能?

时间:2008-09-16 18:37:06

标签: c++ hidden-features

对于“问题线”的“隐藏特征”,没有C ++的爱吗?想我会把它扔出去。 C ++的一些隐藏功能是什么?

64 个答案:

答案 0 :(得分:308)

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

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

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

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

这是

的简写
if (a == 0)
    a = 1;
else
    b = 1;

谨慎使用: - )

答案 1 :(得分:238)

您可以将URI放入C ++源代码而不会出错。例如:

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

    ...
}

答案 2 :(得分:140)

  

指针算术。

由于可以引入的错误,C ++程序员更愿意避免使用指针。

我见过的最酷的C ++? Analog literals.

答案 3 :(得分:119)

我同意那里的大多数帖子:C ++是一种多范式语言,所以你会发现的“隐藏”功能(你应该不惜一切代价避免“未定义的行为”除外)是设施的巧妙使用。 / p>

这些设施中的大部分都不是该语言的内置功能,而是基于库的功能。

最重要的是 RAII ,多年来来自C世界的C ++开发人员经常被忽略。 运算符重载通常是一个被误解的功能,可以启用类似数组的行为(下标运算符),类似指针的操作(智能指针)和类似内置的操作(乘以矩阵。

使用异常通常很困难,但通过一些工作,可以通过异常安全规范生成非常强大的代码(包括不会失败的代码,或者将具有类似提交的功能,这些功能将成功,或恢复到其原始状态。)

C ++中最着名的“隐藏”功能是模板元编程,因为它使您可以在编译时而不是运行时部分(或完全)执行程序。但是这很困难,在尝试之前你必须掌握模板。

其他人使用多范式在C ++的祖先之外产生“编程方式”,即C。

通过使用仿函数,您可以模拟函数,具有额外的类型安全性和有状态。使用命令模式,可以延迟代码执行。大多数其他设计模式可以在C ++中轻松有效地实现,以生成不应该在“官方C ++范例”列表中的替代编码样式。

通过使用模板,您可以生成适用于大多数类型的代码,包括不是您最初想到的代码。您也可以增加类型安全性(如自动类型安全malloc / realloc / free)。 C ++对象功能非常强大(因此,如果不小心使用会很危险),但即使动态多态在C ++中也有静态版本: CRTP

我发现大多数“ Effective C ++ ” - 来自Scott Meyers的书籍或“ Exceptional C ++ ” - 来自Herb Sutter的书籍都很容易阅读,关于C ++的已知和鲜为人知的特性的信息非常珍贵。

在我的首选中,应该让任何Java程序员的头发从恐怖中崛起:在C ++中,向对象添加功能的最面向对象的方式是通过非成员非朋友功能,而不是成员函数(即类方法),因为:

  • 在C ++中,类的接口是其成员函数和同一名称空间中的非成员函数

  • 非朋友非会员功能对内部类没有特权访问权限。因此,对非成员非成员使用成员函数会削弱类的封装。

即使是经验丰富的开发人员也不会感到惊讶。

(资料来源:Herb Sutter的本周在线大师#84:http://www.gotw.ca/gotw/084.htm

答案 4 :(得分:118)

我认为有些隐藏的一种语言功能,因为我在学校的整个过程中从未听说过它,是命名空间别名。直到我在boost文档中遇到它的例子之后才引起我的注意。当然,现在我了解它,你可以在任何标准的C ++参考中找到它。

namespace fs = boost::filesystem;

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

答案 5 :(得分:102)

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

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

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

答案 6 :(得分:77)

数组运算符是关联的。

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

你没说有用...... :-)

答案 7 :(得分:73)

有一点鲜为人知的是,工会也可以是模板:

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

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

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

他们也可以拥有构造函数和成员函数。没有任何与继承(包括虚函数)有关的事情。

答案 8 :(得分:72)

  

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

C ++是一种多范式语言,你可以打赌你的最后一笔钱就是隐藏的功能。很多例子中的一个例子:template metaprogramming。标准委员会中没有人希望有一个图灵完备的子语言在编译时执行。

答案 9 :(得分: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
}

答案 10 :(得分:61)

与const引用相关的临时生命的生命时间是很少有人知道的。或者至少它是我最喜欢的C ++知识,大多数人都不知道。

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

答案 11 :(得分:52)

一个不常用的好功能是功能范围的try-catch块:

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

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

答案 12 :(得分: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; };

答案 13 :(得分: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(int value = getIt()) {
    // ...
}

并在while循环中:

while(SomeThing t = getSomeThing()) {
    // ...
}

(也在for条件下)。但我不太确定这些是否有用:)

答案 14 :(得分:29)

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

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

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

忽略我为条件和代码添加的占位符。重要的是void(),它使编译器强制使用内置逗号运算符。这有时在实现traits类时很有用。

答案 15 :(得分:28)

构造函数中的数组初始化。 例如,如果我们将int数组作为:

,则在类中
class clName
{
  clName();
  int a[10];
};

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

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

答案 16 :(得分:27)

您可以访问任何类的受保护数据和函数成员,没有未定义的行为,并且具有预期的语义。继续阅读,看看如何。另请阅读the defect report

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

答案 17 :(得分:27)

哦,我可以提出一个宠物仇恨列表:

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

在正面

  • 隐藏功能:功能尝试块。不幸的是我没有找到它的用途。是的我知道他们为什么添加它,但是你必须重新使用构造函数,这会使它变得毫无意义。
  • 值得仔细查看容器修改后STL对迭代器有效性的保证,这可以让你做一些稍好的循环。
  • 提升 - 这不是秘密,但值得使用。
  • 返回值优化(不明显,但标准明确允许)
  • Functors aka function objects aka operator()。这被STL广泛使用。这不是真正的秘密,而是操作符重载和模板的一个很好的副作用。

答案 18 :(得分:26)

隐藏的功能:

  1. 纯虚函数可以实现。常见的例子,纯虚析构函数。
  2. 如果函数抛出未在其异常规范中列出的异常,但该函数在其异常规范中具有std::bad_exception,则该异常将转换为std::bad_exception并自动抛出。这样你至少会知道抛出了bad_exception。阅读更多here

  3. 功能尝试块

  4. 模板关键字消除类模板中typedef的歧义。如果成员模板特化的名称显示在.->::运算符之后,并且该名称具有明确限定的模板参数,则在成员模板名称前加上关键字模板。阅读更多here

  5. 函数参数默认值可以在运行时更改。阅读更多here

  6. A[i]的效果与i[A]

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

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

    了解更多here

  9. 如果在三元(:)运算符表达式中?:之前和之后存在两种不同的类型,则表达式的结果类型是最常见的类型他们俩。例如:

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

答案 19 :(得分: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
}

这些被称为“代理呼叫功能”。

答案 20 :(得分: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 ++程序员不知道这一点。

答案 21 :(得分:20)

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

答案 22 :(得分: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++ Templates: The Complete Guide /第8.4节

粘贴了此部分

答案 23 :(得分: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; !
};

答案 24 :(得分:17)

将文件读入字符串向量:

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

istream_iterator

答案 25 :(得分:14)

您可以模拟位域。

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

我还没有提出任何目的,但确实让我感到惊讶。

答案 26 :(得分:14)

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

这些东西中的三个属于一起,两个是完全不同的......

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

第三个和第五个除了在堆栈上定义一个SomeType对象并初始化它(在前两种情况下使用u,在第四种情况下使用默认构造函数。第三种是声明一个函数没有参数并且返回SomeType。第五个类似地声明一个函数,该函数按名为SomeType的类型u的值获取一个参数。

答案 27 :(得分:12)

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

换句话说,可以使用?:运算符

编写以下有效的C ++表达式
i = a > b ? a : throw something();
BTW,throw表达式实际上是表达式(类型为void)而不是语句这一事实是C ++语言的另一个鲜为人知的特性。这意味着,以下代码完全有效

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

尽管这样做并没有多大意义(可能在某些通用模板代码中这可能会很方便)。

答案 28 :(得分: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(); } };

我已经将它用于implement alignment-support,它通过优势规则自动计算出最严格的对齐方式。

这不仅适用于虚函数,也适用于typedef名称,静态/非虚拟成员等等。我看到它曾用于在元程序中实现可覆盖的特征。

答案 29 :(得分: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;

答案 30 :(得分:9)

我发现这个博客是关于C ++的神秘的一个神奇的资源:C++ Truths

答案 31 :(得分:8)

危险的秘密是

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
}

答案 32 :(得分:8)

本地课很棒:

struct MyAwesomeAbstractClass
{ ... };


template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
    struct ans : MyAwesomeAbstractClass
    {
        // Make the implementation depend on T
    };

    return new ans(...);
}

非常简洁,因为它不会使用无用的类定义污染命名空间......

答案 33 :(得分:7)

隐藏到GCC developers的隐藏功能是使用字符串文字初始化数组成员。假设您有一个需要使用C数组的结构,并且您希望使用默认内容初始化数组成员

struct Person {
  char name[255];
  Person():name("???") { }
};

这样可行,并且只适用于char数组和字符串文字初始值设定项。不需要strcpy

答案 34 :(得分:7)

原始类型有构造函数。

int i(3);

作品。

答案 35 :(得分:6)

  

许多例子中的一个:模板   元编程。没人在   标准委员会打算在那里   是图灵完整的子语言   在编译时执行。

模板元编程几乎不是一个隐藏的功能。它甚至在升级库中。见MPL。但如果“几乎隐藏”足够好,那么看看boost libraries。如果没有强大的图书馆支持,它包含许多不易获取的好东西。

一个例子是boost.lambda库,这很有趣,因为C ++在当前标准中没有lambda函数。

另一个例子是Loki,它“广泛使用C ++模板元编程并实现了几种常用的工具:类型列表,仿函数,单例,智能指针,对象工厂,访问者和多方法。” [Wikipedia]

答案 36 :(得分:5)

没有隐藏的功能,但C ++语言非常强大,甚至标准的开发人员也无法想象C ++可以用于什么。

实际上,从简单的语言构造,你可以写出非常强大的东西。 很多这样的东西可以在www.boost.org上找到(以及其中http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html)。

要理解将简单的语言构造与强大的东西结合起来的方式,最好阅读"C++ Templates: The Complete Guide" by David Vandevoorde, Nicolai M. Josuttis和真正的魔法书"Modern C++ Design ... " by Andrei Alexandrescu

最后,学习C ++很难,你应该尝试填充它;)

答案 37 :(得分:4)

答案 38 :(得分:4)

在我看来,只有少数人知道未命名的命名空间:

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]

答案 39 :(得分:3)

来自C++ Truths

在同一范围内定义具有相同签名的函数,因此这是合法的:

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

答案 40 :(得分:3)

我不确定隐藏,但有一些interesting 'tricks'可能只是阅读规范时不明显。

答案 41 :(得分:3)

有很多“未定义的行为”。你可以学习如何避免他们阅读好书和阅读标准。

答案 42 :(得分:3)

大多数C ++开发人员忽略了模板元编程的强大功能。查看Loki Libary。它广泛使用模板元编程实现了几种高级工具,如typelist,functor,singleton,smart pointer,object factory,visitor和multimethods(来自wikipedia)。 在大多数情况下,您可以将这些视为“隐藏的”c ++功能。

答案 43 :(得分:3)

答案 44 :(得分:3)

  • 指向类方法的指针
  • “typename”关键字

答案 45 :(得分:2)

main()不需要返回值:

int main(){}

是最短的有效C ++程序。

答案 46 :(得分:2)

注意自由函数指针和成员函数指针初始化之间的区别:

会员功能:

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

由于这个冗余&amp;,您可以在没有它的情况下添加流操纵器 - 这是免费的功能 -

cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.

答案 47 :(得分:2)

    如果键值已存在,则
  1. map::insert(std::pair(key, value));不会覆盖。

  2. 您可以在定义后立即实例化一个类: (我可能会补充说,由于缺少分号,这个功能给了我数百个编译错误,我从来没有见过有人在课上使用过这个错误)

    class MyClass {public: /* code */} myClass;
    

答案 48 :(得分:1)

如果operator delete()除了* void之外还接受size参数,这意味着它将是一个基类。该size参数可以检查类型的大小,以便销毁正确的类型。这里Stephen Dewhurst讲述了这个:

  

另请注意,我们已经雇佣了一名   运算符的双参数版本   删除而不是通常   单参数版本。这个   两个论点的版本是另一个   成员运营商的“通常”版本   删除通常由基类使用   期望派生类继承   他们的运营商删除实施   第二个参数将包含   对象的大小   经常删除的信息   有用的实现自定义内存   管理。

答案 49 :(得分:1)

Indirect Conversion Idiom

  

假设你正在设计智能   指针类。此外   重载运算符*和 - &gt;,a   智能指针类通常定义一个   转换运算符为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;
  

此外,隐式转换   有条件地要求布尔   声明如:

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-&gt; bool而不是bool-&gt;别的东西。

答案 50 :(得分:1)

类和结构类键几乎相同。主要区别在于类默认为成员和基础的私有访问,而结构默认为public:

// 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

联盟也可以拥有成员和方法,默认为与结构类似的公共访问。

答案 51 :(得分:1)

C ++中有很多“棘手”的结构。 它们来自使用虚拟继承的sealed/final classes的“简单”实现。 并获得漂亮的“复杂”元编程结构,例如Boost的MPLtutorial)。在脚下拍摄自己的可能性是无穷无尽的,但如果保持警惕(即经验丰富的程序员),在可维护性和性能方面提供一些最佳的灵活性。

答案 52 :(得分:1)

我发现递归模板的实例非常酷:

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需要为每个值提供一个函数指针。

即。编程编译器通过递归生成一堆函数。非常简单。 :)

答案 53 :(得分:0)

您可以通过命令行开关使用某些编译器查看所有预定义的宏。这适用于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,它们列在single place中。它们也可以在其他地方记录在一个地方,但是使用上面的命令,您可以清楚地查看什么是未定义的,以及确切地使用了什么值,在应用所有其他命令之后线开关。

比较(排序后):

 $ diff gxx-macros.txt icx-macros.txt
 $ diff gxx-macros.txt gcc-macros.txt
 $ diff icx-macros.txt icc-macros.txt

答案 54 :(得分:0)

constraints添加到模板。

答案 55 :(得分:0)

我最喜欢的(暂时)是在一个声明中缺乏语义 A = B = C。 A的价值基本上没有确定。

想一想:

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无关的值。

答案 56 :(得分:0)

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;" */
}

答案 57 :(得分:0)

成员指针和成员指针运算符 - &gt; *

#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 - &gt; *&amp; A :: e)()有点像来自javascript的Function.call()

var f = A.e
f.call(a) 

对于会员而言,有点像使用[]运算符

进行访问
a['d']

答案 58 :(得分:-1)

使用静态广告模拟重新解释广告

int var;
string *str = reinterpret_cast<string*>(&var);

以上代码相当于以下内容:

int var;    
string *str = static_cast<string*>(static_cast<void*>(&var));

答案 59 :(得分:-2)

模板元编程是。

答案 60 :(得分:-2)

实际上并不是隐藏的功能,而是纯粹的超棒:

#define private public 

答案 61 :(得分:-2)

您可以将变量引用作为函数的一部分返回。它有一些用途,主要用于产生可怕的代码:

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
}

答案 62 :(得分:-2)

指针算术。

它实际上是一个C功能,但我注意到很少有使用C / C ++的人真的意识到它甚至存在。我认为C语言的这一特性真正展示了其发明者的天才和愿景。

总而言之,指针算术允许编译器对任何类型的a执行[n]为*(a + n)。作为旁注,由于'+'是可交换的,[n]当然等于n [a]。

答案 63 :(得分:-3)

我知道有人只用一种方法同时定义了一个getter和一个setter。像这样:

class foo
{
    int x;

    int* GetX(){
        return &x;
    }
}

你现在可以像往常一样使用它作为一个吸气剂(好吧,差不多):

int a = *GetX();

作为二传手:

*GetX() = 17;