it-swarm.cn

C++的隐藏功能?

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

114
Craig H

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

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

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

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

这是简写

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

谨慎使用:-)

308
Ferruccio

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

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

    ...
}
238
Ben

指针算术。

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

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

140
Anonymouse

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

大多数这些设施不是语言的内置功能,而是基于库的功能。

最重要的是 _ raii _ ,经常被来自C世界的C++开发人员忽略多年。 运算符重载 通常是一个误解的特性,它支持类似数组的行为(下标运算符),类似指针的操作(智能指针)和类似内置的操作(乘法矩阵)。

使用 exception 通常很困难,但是通过一些工作,可以通过 exception safety specifications(包括不会失败的代码,或者会有一个提交的代码)生成非常健壮的代码喜欢成功的功能,或恢复到原始状态的功能)。

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

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

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

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

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

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

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

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

这甚至不会让经验丰富.

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

119
paercebal

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

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );
118
Jason Mock

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

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

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

102
Johannes Schaub - litb

数组运算符是关联的。

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

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

77
Colin Jensen

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

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

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

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

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

73
Johannes Schaub - litb

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

C++是一种多范式语言,你可以打赌你的最后一笔钱是隐藏的功能。许多例子中的一个例子: 模板元编程 。标准委员会中没有人打算在编译时执行图灵完整的子语言。

72
Konrad Rudolph

另一个在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
}
66
Johannes Schaub - litb

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

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

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

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

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

52
vividos

许多人都知道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; };
44
Johannes Schaub - litb

一个非常隐蔽的功能是您可以在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()) {
    // ...
}

(也适用于条件)。但我不太确定这些是否都有用:)

43
Johannes Schaub - litb

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

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

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

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

29
Johannes Schaub - litb

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

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

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

clName::clName() : a()
{
}
28
Poorna

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

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

从积极的一面

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

您可以访问任何类的受保护数据和函数成员,没有未定义的行为,并具有预期的语义。继续阅读,看看如何。另请阅读 缺陷报告 关于此。

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

当然,正如你所看到的那样会造成太大的伤害。但现在,成员指针允许绕过这种保护!关键点在于成员指针的类型绑定到实际包含所述成员的类 - not 到您在获取地址时指定的类。这允许我们规避检查

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);
}
27
Johannes Schaub - litb

隐藏功能:

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

  3. 功能尝试块

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

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

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

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

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

    阅读更多 这里

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

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

另一个隐藏的功能是您可以调用可以转换为函数指针或引用的类对象。对它们的结果进行重载分辨率,并且完全转发参数。

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
}

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

26
Johannes Schaub - litb

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

24
Constantin

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

20
Jim Hunziker

在类模板中定义普通的朋友函数需要特别注意:

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节

19
Özgür

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; !
};
18
Johannes Schaub - litb

将文件读入字符串向量:

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

istream_iterator

17
Jason Baker

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

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

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

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

14
Eclipse

您可以模板位域。

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

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

14
Kaz Dragon

优势规则很有用,但鲜为人知。它表示即使在通过基类网格的非唯一路径中,如果成员属于虚拟基类,则部分隐藏成员的名称查找也是唯一的:

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

12
Johannes Schaub - litb

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

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

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

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

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

虽然这样做没有多大意义(可能在一些通用的模板代码中,这可能会派上用场)。

12
AnT

摆脱前瞻性声明:

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;
12
AareP

我发现这个博客是一个关于C++的晦涩难懂的资源: C++ Truths

9
Drealmer

一个危险的秘密是

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
}
8
user34537

本地课程很棒:

struct MyAwesomeAbstractClass
{ ... };


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

    return new ans(...);
}

非常整洁,因为它没有用无用的类定义污染命名空间......

8
Alexandre C.

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

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

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

7
Johannes Schaub - litb

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

模板元编程几乎不是隐藏的功能。它甚至在升级库中。见 _ mpl _ 。但如果“几乎隐藏”足够好,那么看看 boost库 。它包含许多好东西,没有强大的库的支持,不容易访问。

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

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

6
Markowitch

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

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

为了理解简单的语言结构如何与强大的东西结合起来的方式,最好阅读 “C++模板:完整指南”,David Vandevoorde,Nicolai M. Josuttis 和真正的魔法书 “现代C++设计。 ..“作者:Andrei Alexandrescu

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

5
sergtk

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

namespace {
  // Classes, functions, and objects here.
}

未命名的命名空间的行为就像它们被替换为:

namespace __unique_{ /* empty body */ }
using namespace __unique_name__;
namespace __unique_{
  // original namespace body
}

“..翻译单元中[此唯一名称]的所有出现都被相同的标识符替换,并且该标识符与整个程序中的所有其他标识符不同。” [C++ 03,7.3.1.1/1]

4
vobject
4
Özgür

我不确定隐藏,但有一些 有趣'技巧' 这可能只是阅读规范不明显。

3
dbrien

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

3
ugasoft

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

3
Sridhar Iyer

来自 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";
}
3
Özgür
  • 指向类方法的指针
  • “typename”关键字
3
shoosh
3
sdcvvc

main()不需要返回值:

int main(){}

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

2
Jeffrey Faust

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

会员功能:

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.
2
Özgür
  1. 如果键值已存在,map::insert(std::pair(key, value));不会覆盖。

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

    class MyClass {public: /* code */} myClass;
    
2
Viktor Sehr

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

1
Amir

类和结构类键几乎相同。主要区别在于类默认为成员和基础的私有访问,而结构默认为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

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

1
a_m0d

间接转换成语

假设您正在设计智能指针类。除了重载运算符*和 - >之外,智能指针类通常将转换运算符定义为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->某事其他。

1
Özgür

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

另请注意,我们使用了操作符删除的双参数版本,而不是通常的单参数版本。这个双参数版本是成员运算符delete的另一个“通常”版本,通常由基类使用,期望派生类继承其运算符删除实现。第二个参数将包含要删除的对象的大小 - 通常在实现自定义内存管理时有用的信息。

1
Özgür

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

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

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

1
Macke

我最喜欢的(目前)是在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无关的值。

0
Rune FS

添加 约束 到模板。

0
Özgür

成员指针和成员指针运算符 - > *

#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']
0
Kamil Szot

您可以通过命令行开关使用某些编译器查看所有预定义的宏。这适用于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
0
Roger Pate
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;" */
}
0
dirkgently