C++

Table of Contents

1. C++

1.1. c++容器当pop元素时元素如果被自动析构

Test t; vector<Test> v; v.push_back(t); v.pop_back(); v pop_back时,容器中相应的元素(t的拷贝)被自动析构. 但 Test * t=new Test; v.push_back(t); v.pop_back()时,t指向的元素却并不会被析构. 同样,若vector中元素为其它内置类型也,也不可能被自动析构

vector的源码中, pop_back() { __M_content; destroy(..); } 而destroy(p)只是简单的调用 p->~T()

为了容器的一致,C++规定,内置类型的析构也能编译通过,但这些析构实际上什么也不做, 即当T为int时,p->~int()也能编译通过,指针也属于内置类型,所以容器中为指针时,也能编译通过,但析构时实际上什么也不做.

从上面vector的代码中也可以看到,vector pop_back时并不释放内存,而list与此不同: list的pop_back大致为: destroy(); deallocate(); .. 其中deallocate负责释放内存

上面提到的destroy,deallocate,还有allocate,construct都是容器的allocator的功能, 可以自己重写一个allocator,实现不同的allocate,deallocate,construct,destroy行为,然后把新的allocator做为容器的模板参数

1.2. c++引入引用的最根本原因

c++引入引用,是因为容器的存在.

对比C语言中经典的容器:数组. 数组可以保存各种类型,包括内置类型,自定义struct等, 如array[0]=structA,则后面array[0].value1=1时,即是修改structA,数组的[]是C内置运算符,不存在array[0]取得的是structA的拷贝的问题

但在c++中,容器类的[],at,get…都是函数,如vector<struct> v; v[0]=structA; v[0].value=1;若operator[]直接返回struct类型,而不是引用或指针,则v[0].value=1修改的实际上是structA的拷贝.所以C++的容器类的getter,为了与c语义上的一致,都是返回引用.如果没有引用,C++容器只能设计成必须存储指针类型

另外,用c写的glib中容器,如garray,getter都是使用宏来完成的,宏可以认为是 return-by-name, 和引用的效果类似.如 #define g_array_index(a,t,i) (((t*) (void *) (a)->data) [(i)]), a是array,t是容器中成员类型,i是偏移量

综上:容器类的getter方法都是函数调用,为了避免return-by-value,使用了 return-by-name

1.3. operator->()

operator->()用来使某个类像指针,如iteraotr

1.4. [23.11] How can I set up my class so it won't be inherited from?

This is known as making the class "final" or "a leaf." There are three ways to do it: an easy technical approach, an even easier non-technical approach, and a slightly trickier technical approach.

The (easy) technical approach is to make the class's constructors private and to use the Named Constructor Idiom to create the objects. No one can create objects of a derived class since the base class's constructor will be inaccessible. The "named constructors" themselves could return by pointer if you want your objects allocated by new or they could return by value if you want the objects created on the stack.

The (even easier) non-technical approach is to put a big fat ugly comment next to the class definition. The comment could say, for example, / We'll fire you if you inherit from this class or even just /*final* class Whatever {…};. Some programmers balk at this because it is enforced by people rather than by technology, but don't knock it on face value: it is quite effective in practice.

A slightly trickier technical approach is to exploit virtual inheritance. Since the most derived class's ctor needs to directly call the virtual base class's ctor, the following guarantees that no concrete class can inherit from class Fred:

class Fred;

class FredBase {
protected:
  FredBase() { }
};

class Fred : private virtual FredBase {
public:
...
};

Class Fred can access FredBase's ctor, since Fred is deprived from FredBase, but no class derived from Fred can access FredBase's ctor (note 1),and therefore no one can create a concrete class derived from Fred.

note 1: 'no class derived from Fred can access FredBase's ctor' need 3 constrains be considered:

  1. FredBase() must be case1: protected and Fred derives from Fred or case2: FredBase() is private , Fred derived from FredBase and is a friend of Fred
  2. Fred must private inherits from FredBase on case 1 or protected inherits from FredBase on case 2
  3. virtual inherits is a must for Fred, because:

    When a base class is inherited virtualy, it is up to the most derived class to initialize it.

    in our case, if class F deprived from Fred, it's F's duty to initialize Fred, which is impossible since Fred's ctor is not accessable.

    about virtual inheritance, check attached virtual.cpp

~@sunway-lab> ./a.out
ctor for FredBase called
ctor for Fred called
ctor for Fred2 called

1.5. never throw exceptions in dtor

[17.3] How can I handle a destructor that fails?

Write a message to a log-file. Or call Aunt Tilda. But do not throw an exception! Here's why (buckle your seat-belts):

The C++ rule is that you must never throw an exception from a destructor that is being called during the "stack unwinding" process of another exception. For example, if someone says throw Foo(), the stack will be unwound so all the stack frames between the throw Foo() and the } catch (Foo e) { will get popped. This is called stack unwinding.

During stack unwinding, all the local objects in all those stack frames are destructed. If one of those destructors throws an exception (say it throws a Bar object), the C++ runtime system is in a no-win situation: should it ignore the Bar and end up in the } catch (Foo e) { where it was originally headed? Should it ignore the Foo and look for a } catch (Bar e) { handler? There is no good answer ― either choice loses information.

So the C++ language guarantees that it will call terminate() at this point, and terminate() kills the process. Bang you're dead.

The easy way to prevent this is never throw an exception from a destructor. But if you really want to be clever, you can say never throw an exception from a destructor while processing another exception. But in this second case, you're in a difficult situation: the destructor itself needs code to handle both throwing an exception and doing "something else", and the caller has no guarantees as to what might happen when the destructor detects an error (it might throw an exception, it might do "something else"). So the whole solution is harder to write. So the easy thing to do is always do "something else". That is, never throw an exception from a destructor.

Of course the word never should be "in quotes" since there is always some situation somewhere where the rule won't hold. But certainly at least 99% of the time this is a good rule of thumb.

1.6. what is type-safe and strong type

http://hi.baidu.com/chenfalei/blog/item/f33ac0133500ac21dd540186.html 没有绝对的类型安全与强类型语言。类型安全都是相对的。比如C/C++基本的类型检查保证了一部分的类型安全,但它的 union/指针/强制转换/数组 却破坏了类型安全。绝对的类型安全是指:一旦程序通过编译,即不再存在除逻辑错误外的其他错误。

1.7. why pop_back returns void instead of the removed object?

  • The SGI's docs contain the reasoning - it's from queue's documentation but it also applies for deque: "One might wonder why pop() returns void, instead of value_type. That is, why must one use front() and pop() to examine and remove the element at the front of the queue, instead of combining the two in a single member function? In fact, there is a good reason for this design. If pop() returned the front element, it would have to return by value rather than by reference: return by reference would create a dangling pointer. Return by value, however, is inefficient: it involves at least one redundant copy constructor call. Since it is impossible for pop() to return a value in such a way as to be both efficient and correct, it is more sensible for it to return no value at all and to require clients to use front() to inspect the value at the front of the queue."
  • exception safety 假设 pop_back()定义为:
T topValue=vector1.pop();

T pop_back() {
    T temp=vec[top];
    --top;
    return temp; // what about if the copy assignment or copy constructor of temp->topValue fails?
}

1.8. 数组蜕化为指针及数组引用做为函数参数

#include <iostream>
using namespace std;

void f (int (& a) [10]) {
    cout<<a[0]<<endl;
    cout<<sizeof(a)<<endl;
}
void g (int a []) {
    f (a);
}
int
main(int argc, char *argv[]) {
    int a[10]={9};
    g (a);
    return 0;
}

编译错误

#include <iostream>
using namespace std;

void f (int (& a) [10]) {
    cout<<a[0]<<endl;
    cout<<sizeof(a)<<endl;
}
int
main(int argc, char *argv[]) {
    int a[10]={9};
    f (a);
    return 0;
}

编译正确

可见,数组作为参数会蜕化为指针,但数组引用做为参数不会

1.9. c++ template 链接问题

or 为什么c++ template的声明和定义通常在同一个文件,而不把定义单独编译成目标文件因为c++ template is instantiated at compile time. http://en.wikibooks.org/wiki/C%2B%2B_Programming/Template

1.10. The Standard Librarian: Containers of Incomplete Types

1.11. 临时对象的const引用

string foo() {return string("abc");} const string & a=foo(); 是合法的,这是C++对大量存在的 foo (const & T) 类型的函数的折衷 string & a=foo()是非法的类似的,对于foo("abc")的调用,void foo(const string & s)是合法的,void foo(string & s)是非法的,除非 string a("abc");string & b=a; foo(b);

虽然c++要求返回的临时对象的引用必须是const,但在gcc里,复杂对象通常都是用 named-return-value,所以返回的临时对象实际上必然在调用者的栈中,所以

main (int argc, char * argv[]) {
 const T & t=fun ();
 T & tt=const_cast<T &>(t);
 tt.a=19;
 printf ("%d\n",t.a);
 return 0;
 }

并没有问题 main的栈: 临时对象a 引用t的指针,指向临时对象a

1.12. bitwise copy

http://blogs.msdn.com/slippman/archive/2004/01/20/60655.aspx http://www.cppblog.com/jerysun0818/archive/2006/05/05/6632.html

In practice, a good compiler can generate bitwise copies for most class objects since they have bitwise copy semantics…. That is, a copy constructor is not automatically generated by the compiler for each class that does not explicitly define one.

Default constructors and copy constructors…are generated (by the compiler) where needed. Needed in this instance means when the class does not exhibit bitwise copy semantics When are bitwise copy semantics not exhibited by a class? There are four instances:

  1. When the class contains a member object of a class for which a copy constructor exists (either explicitly declared by the class designer, as in the case of the previous String class, or synthesized by the compiler, as in the case of class Word)
  2. When the class is derived from a base class for which a copy constructor exists (again, either explicitly declared or synthesized)
  3. When the class declares one or more virtual functions
  4. When the class is derived from an inheritance chain in which one or more base classes are virtual

如果一个类的成员中有指针成员,如果它又有bitwise copy semantics(如无虚函数,成员类没有定义copy constructor..),默认会使用bitwise copy,但是对指针做shadow copy在对象析构时指针成员可能会被多次delete,所以对于这个类要自定义copy constructor使其丧失bitwise copy semantics

1.13. 可怜的bool

发信人: Oversense (空), 信区: CPlusPlus 标 题: 可怜的bool 发信站: BBS 水木清华站 (Mon Oct 21 17:53:11 2002), 转信

可怜的bool

作者: Jim Hyslop 和 Herb Sutter 翻译:oversense <17:33 2002-10-21> 出处: http://www.cuj.com/experts/2011/hyslop.htm?topic=experts

嘿嘿…今天的活比较爽!前几天写了点破程序,今天改改就搞定了。哎,真困!喝点咖啡,靠在我的小椅子上,看看我的代码…

神奇,这是啥? void f() { TextHandler t; t.sendText("Hello, world", true); // … }

后面那个true是什么东东?翻翻定义: class TextHandler { public: void sendText( const std::string & msg, bool sendNewLine ); //… };

喝点coffee,我想起来了,true表示sendText函数自动加上一个回车换行,我怎么忘了?难道是我笨?我陷入迷茫的沉思…

砰!!!的一声巨响,我一慌张,嘴里的咖啡差点喷出来,还好我嘴紧。一定是Guru合上了她的什么大头书。我转向她,挤出一点微笑。她手里拿着一本不到一百页的小册子。神奇,这么小的书弄得这么响,她是怎么弄得?

显然,她什么都知道了。

"我的宝贝!如果你这么快就忘了参数的含义,那么当其他程序员第一次看你的代码的时候,他怎么明白你要表达的意思呢?"

"嗯,是啊" 我咕噜道 "但是在IDE里面,他只要把鼠标移到函数上,他就可以看到参数说明了啊!"

"有些IDE如此,并非全部,甚至不是大多数!我说过很多次,源代码最主要的用途是用来交流,对意图的交流。我手中这本古老的,令人尊敬的卷册阐述了交流的艺术。在这儿,它写道,'使用明确,详细,具体的语言'[1],你代码中的bool与此无缘,他不能传递任何有用的信息给读者。 "

"不哈,一旦他知道这个bool是什么意思,就很容易记住了哈!"我中气不足的说。

Guru用她美丽而坚定的蓝眼睛盯着我,我心里扑通扑通跳起来。

"你多久以前写的这段代码?"Guru很温柔的说道–那种我喜欢的温柔。 "嗯,好,嗯,那如何改正呢?"我巧妙的回避她的问题。 "你不能另外想一种方式去表达你的意图吗?"她也不直接回答我,我们就好像在煎鸡蛋。

"我可以不要第二个参数,让用户自己加 '\n' 好了。"我边说边写: { t.sendText("Hello, world\n"); } "如果传递给sendText是一个变量呢?" Guru问。 "那就这样好了" { t.sendText( variable ); t.sendText( "\n" ); } 我抬头看到Guru脸色不善,赶紧说道: "那就这样,我提供两个函数" void sendText( const std::string & ); void sendTextWithNewLine( const std::string & );

"没有其他的办法了吗?"Guru思考的时候,微微皱眉。哎,看来我今天不要想轻松溜走了,我一阵猛想……什么也没想出来,我投降了,"就这样吧!"

"关于你的问题,你还要认识一点" Guru写道: void displayText( const std::string &, bool applyItalics, bool applyBold ); void f(){ displayText( "This is bold but not italic",true, false ); } "如果一个程序员要用斜体显示文字,但是弄错了参数顺序,那么这些文字就要用粗体显示了,而且显然编译器无法发现这个错误。"

"如果Bob拿到了这段代码,改变了参数的顺序,一种叫做'Permute And Baffle' 的技术[2]。会怎样?"

"显然,问题多多的displayText不能得到 '明确,详细,具体' 的参数。"

"现在来看你的问题,你的第一个方案,因为displayText需要其他参数而没法用。你的第二个方案,可以工作,但是如果displayText需要很多信息,比如颜色,字体等,你是不是要提供如此多的函数呢?"

"所以,我们可以用enumerated." "Enumerations?"我奇怪的说。

"是的,Enumerations在这儿能得到很好的应用,看," class TextHandler { public: enum NewLineDisposition { sendNewLine, noNewLine }; void sendText( const std::string &, NewLineDisposition ); }; void f() { TextHandler t; t.sendText( "Hello, ", TextHandler::noNewLine ); t.sendText( "world", TextHandler::sendNewLine ); } "这种写法很好,这段代码现在self-documenting了,不需要注释,意图和结果都很清楚。看得人不需要去查找函数的定义了。" "而且,这种写法有很好的扩展性,如果你需要只加一个回车,你只需要在enume ration中加上prependNewLine就可以了,现有的代码无需任何改变。"

"不要抛弃你第一个方案,宝贝。在有的情况下,他是最优的。"Guru转过身,重新打开那本书,优雅而轻快的走开,消失在拐角处。

注解: [1] William Strunk Jr. and E.B. White. The Elements of Style (MacMilla n Publishing Co. Ltd, 1979). [2] From Roedy Green's "How To Write Unmaintainable Code," http://mindprod.com/unmaindesign.html. (Primarily aimed at Java prog rammers, it still has lots of relevance for C++ programmers.)

1.14. operator new

http://www.scs.cs.nyu.edu/~dm/c++-new.html

#include <iostream>
using namespace std;

class Test {
    int val;
public:
    Test (int v):val (v) {cout<<val<<endl;}
    void * operator new (size_t size,int count) {cout<<__LINE__<<endl;return malloc (size);}
    void operator delete( void * p,size_t size ) {cout<<size<<endl;free (p);}
    //new和delete只负责分配释放内存,不负责ctor和dtor调用
};

int
main(int argc, char ** argv) {
    Test * t=new (10) Test (20);
    Test tt; //局部变量不使用operator new和operator delete
    return 0;
}

1.15. prefrer i to i

For builtin types, it really doesn't matter. But in C++, you can write and operator++ for your own class. And then it might matter, becaure postfix + has to create a copy of the object so that the old value can be returned. If you don't need the return value, that copy is unnecessary. If the compiler doesn't do named return value optimization, that copy might even need to be copied again, and all that just to throw the result away. The postfix operator+ for an own class might look something like this:

MyClass MyClass::operator++(int)
{
MyClass retval(*this); // copy the object
// do whatever is needed to "increment" the object
reutrn retval; // return the copy by value
}

while prefix ++ might look like:

MyClass& MyClass::operator++()
{
// do whatever is needed to "increment" the object
return *this; // return a refernce to the object
}

Therefore, it's considered a good habit to always use prefix ++ if the return value is not needed.

1.16. Decorator pattern

http://en.wikipedia.org/wiki/Decorator_pattern

#include <iostream>
using namespace std;

class B {
public:
    virtual void fun () {cout<<"C"<<endl;}
};

class Dec:public B {
    B * pbase;
public:
    Dec (B * b):pbase(b) {}
    virtual void fun () {cout<<"Dec"<<endl;pbase->fun ();}
};

int
main(int argc, char ** argv) {
    B * b=new Dec(new Dec (new B));
    b->fun ();

    return 0;
}

1.17. 一个Printable类

#include <iostream>
#include <string>
#include <sstream>
using namespace std;
class Printable {
    virtual string to_str () const=0;
public:
    friend ostream & operator<<(ostream &,const Printable &);
};

ostream & operator<<(ostream & o,const Printable & p) {
    return o<<p.to_str ()<<endl;
}

class Test:public Printable {
    int a;
    int b;
private:
    string to_str () const {
	stringstream ss;
	ss<<a<<" ";
	ss<<b<<endl;
	return ss.str ();
    }
public:
    Test (int a,int b):a (a),b (b) {}
};
int
main(int argc, char ** argv) {
    Test t (1,2);
    cout<<t;
    return 0;
}

1.18. 类型转换

假设需要一个X到Y的转换:

  • Y内定义 Y(const X &) ()构造函数
  • X内定义 operator Y() {return Y;}函数

    自定义的类型转换也可以被隐式调用,例如 Y实现了 friend ostream & operator <<(ostream &,const Y &) 使Y可以通过 cout<<Y的形式调用,如果在Y或X中定义了X到Y的转换,则cout<<X时,X会被隐式的转换为Y并调用operator<<(cout,Y) 另外,自定义的类型转换也是static_cast能否成功的根据之一

1.19. cast

  • 'reinterpret_cast' 只能用于指针或引用'reinterpret_cast' casts a pointer to any other type of pointer. It also allows casting from pointer to an integer type and vice versa. This operator can cast pointers between non-related classed. The operation results is a simple binary copy of the value from a pointer to the other. The content pointed does not pass any kind of check nor transformation between types. In the case that the copy is performed from a pointer to an integer, the interpretation of its content is system dependent and therefore any implementation is non portable. A pointer casted to an integer enough large to fully contain it can be casted back to a valid pointer.

    Code:

class A {};
class B {};

A * a = new A;
B * b = reinterpret_cast<B *>(a);

'reinterpret_cast' treats all pointers exactly as traditional type-casting operators do.

  • 'dynamic_cast' 只能用于指针或引用

    'dynamic_cast' is exclusively used with pointers and references to objects. It allows any type-casting that can be implicitly performed as well as the inverse one when used with polymorphic classes, however, unlike static_cast, dynamic_cast checks, in this last case, if the operation is valid. That is to say, it checks if the casting is going to return a valid complete object of the requested type. Checking is performed during run-time execution. If the pointer being casted is not a pointer to a valid complete object of the requested type, the value returned is a 'NULL' pointer.

    dynamic_cast使用了RTTI来确定能否转换成功,而只有支持多态的类(有虚函数)才在 vtbl中有相应的RTTI信息,所以:

class Base {

};
class Derived:public Base {

};

Base * b=new Derived();
dynamic_cast<Derived *>(b)不会成功,因为没有vtbl,即没有RTTI信息

Code:

class Base { virtual dummy() {} };
class Derived : public Base {};

Base* b1 = new Derived;
Base* b2 = new Base;

Derived* d1 = dynamic_cast<Derived *>(b1);          // succeeds
Derived* d2 = dynamic_cast<Derived *>(b2);          // fails: returns 'NULL'

If the type-casting is performed to a reference type and this casting is not possible an exception of type 'bad_cast' is thrown:

Code:

class Base { virtual dummy() {} };
class Derived : public Base { };

Base* b1 = new Derived;
Base* b2 = new Base;

Derived d1 = dynamic_cast<Derived &*>(b1);          // succeeds
Derived d2 = dynamic_cast<Derived &*>(b2);          // fails: exception thrown
  • 'static_cast'

    'static_cast' allows to perform any casting that can be implicitly performed as well as also the inverse cast (even if this is not allowed implicitly). Applied to pointers to classes, that is to say that it allows to cast a pointer of a derived class to its base class (this is a valid conversion that can be implicitly performed) and can also perform the inverse: cast a base class to its derivated class. In this last case the base class that is being casted is not checked to determine wether this is a complete class of the destination type or not. Code:

class Base {}; class
Derived : public Base {};

Base *a    = new Base;
Derived *b = static_cast<Derived *>(a);
'static_cast', aside from manipulating pointers to classes, can also be used to perform conversions explicitly defined in classes, as well as to perform standard conversions between fundamental types:
Code:
double d = 3.14159265;
int    i = static_cast<int>(d);
  • 'const_cast'

    This type of casting manipulates the const attribute of the passed object, either to be set or removed:

    Code:

class C {};
const C *a = new C;
C *b = const_cast<C *>(a);

Neither of the other three new cast operators can modify the constness of an object. Notes: It is undefined behaviour if the pointer is used to write on an constant object (an object declared as 'const'). The 'const_cast' operator can also change the 'volatile' qualifier on a type.

总结: reinterpret_cast是不懂c++语法的,也不会使用rtti信息,这就决定了它不能解析c++的类结构,如多重继承,虚拟继承这些复杂的结构. 指针的reinterpret_cast的过程中,值是不会变的,改变的只是对这个值的处理方式(当作c的指针还是当作d的指针)

static_cast是懂c++的语法的,它能了解编译时的ctti信息,能够在处理多重继承,虚拟继承等复杂结构. B C1:virtual B C2:virtual B D:C1,C2 D * d=new D()能成功static_cast到B*,C1*或C2*,因为static_cast了解继承的树

static_cast是编译时的行为,它不能正确处理的情况是: B C1:virtual B C2:virtual B D:C1,C2 void foo(B * b) { D * d=static_cast<D >(b); } 如果: B * b=new B(); foo(b); 程序会出错,因为static_cast不知道foo的参数b到底是不是真的D,编译时的static_cast只能假设它是了

这时就需要dynamic_cast了,dynamic_cast的static_cast的功能基本相同,但它使用了 rtti信息,可以正确处理从基类到派生类的转换,由于使用了rtti,只有有虚函数或虚基类的类才能使用.

另外,c++中的(B *)d式的转换相当于编译器按 const_cast,static_cast,dynamic_cast,reinterpret_cast的顺序挨个测试能否转换成功,reinterpret_cast是最后的选择

1.20. RTTI与CTTI

RTTI是在多态类的vtbl中存储的类型信息,非多态类没有RTTI信息 dynamic_cast和typeid需要使用RTTI信息 typeid可以得到类型信息,如typeid(1).name(),或typeid(myclass).name() 但并非所有的typeid都是使用了RTTI,因为:

  • typeid是运算符,而不是函数,所以typeid的值可能在编译时就能确定
  • 对于非多态类型,如内置数据类型,或自定义的非多态的类,typeid使用的是CTTI,即编译时就确定类型而对于多态类型,typeid通过查询多态类的vtbl中的RTTI信息确定类型

1.21. 重载 <<

#include <iostream>
using std::cout;
using std::endl;

struct foo {
    short a;
    char  b;
    char  c;
    int   d;
};

int
main(int argc, char ** argv) {
    int c=('A' << 16) + ('B' << 8) + 'C';
    foo f = {1234, 'x', 'y', ('D' << 24 )+('A' << 16) + ('B' << 8) + 'C'};
    cout<<&f.b<<endl;
    return 0;
}


#include <iostream>
#include <iostream>
using namespace std;


class foo {
    short a;
    char  b;
    char  c;
    int   d;
public:
    foo (short a,char b,char c,int d):a (a),b (b),c (c),d (d) {}
    friend ostream& operator<<(ostream& output,const foo & f);

};

ostream & operator<<(ostream& output,const foo & f) {
    output<<f.a<<endl;
}
int
main(int argc, char ** argv) {
    int c=('A' << 16) + ('B' << 8) + 'C';
    foo f (1234, 'x', 'y', ('D' << 24 )+('A' << 16) + ('B' << 8) + 'C');
    cout<<f;
    return 0;
}

1.22. mask

string mask (const string & ip, const string & mask) {
//{{{
struct sockaddr_in servaddr;
bzero (&servaddr, sizeof (servaddr));
servaddr.sin_family = AF_INET;
inet_pton (AF_INET, ip.c_str (), &servaddr.sin_addr);
in_addr_t ip_digit=servaddr.sin_addr.s_addr;
inet_pton (AF_INET, mask.c_str (), &servaddr.sin_addr);
in_addr_t mask_digit=servaddr.sin_addr.s_addr;
in_addr_t tmp=ip_digit&mask_digit;
servaddr.sin_addr.s_addr=tmp;
char * ret=(char *)malloc (20);
inet_ntop (AF_INET,&servaddr.sin_addr,ret,20);
string rets=string (ret);
free (ret);
return rets;
}
//}}}

1.23. copy构造函数与vtl

#include <iostream>
#include <string>
using namespace std;
class A {
    int a;
public:
    A () {}
    A (const A & a) {memcpy (this,&a,sizeof(a));}
    virtual void fun () {cout<<"A"<<endl;}

};

class B:public A {
public:
    B (){}
    void fun () {cout<<"B"<<endl;}

};
int
main(int argc, char ** argv) {
    B b;
    A a=b;
    A * ptra=&a;
    ptra->fun ();
    return 0;
}

1.24. 类对象的几种声明方法:

使用构造函数和copy构造函数(但copy构造函数的调用通常被编译器优化掉)

  • A a=A();
  • A a=A(1);

没使用copy构造函数

  • A a;
  • A a(1);

使用了copy构造函数但被优化掉和没使用copy构造函数的区别是:当copy构造函数为private时….

指针形式

  • A * a=new A()
  • A * a=new A(1)

注意:

  • A a() 并不等同于 A a=A(),虽然它和A a(1)很像,实际上 A a()是一个函数声明……a is a function returning A……

1.25. virtual函数,vptr例子

#include <iostream>
#include <string>
using namespace std;
class A {

public:
    virtual void fun () {cout<<"A"<<endl;}
};
class B {

public:
    virtual void fun () {cout<<"B"<<endl;}
};

int
main(int argc, char ** argv) {
    A * a=new A;
    B * b=new B;
    void * ptra=reinterpret_cast<void *>(a);
    void * ptrb=reinterpret_cast<void *>(b);
    memcpy (ptrb,ptra,1);
    b->fun ();
    return 0;
}

1.26. 成员函数的调用

#include <iostream>
using namespace std;

class AA {
    static int foo;
public:
    void fun ();
};
class AAA:public AA {
public:
};
void AA::fun() {
    cout<<"AA"<<endl;
}

int
main(int argc, char ** argv) {
    AA a;
    AAA aa;
    void (* f)(AA *)=reinterpret_cast<void (*)(AA *)>(0x080486d8);
    f (&a);
    return 0;
}

1.27. virtual析构函数

<iostream>
using std::cout;
using std::endl;

class Base{
public:
virtual ~Base(){cout<<"~B"<<endl;}
};

class Derived:public Base{
public:
virtual ~Derived(){cout<<"~D"<<endl;}
};

void main (){
Base *b=new Derived();
delete b;
}

//执行结果

~D
~B

//解释沿着程序执行流程,从main函数进入首先,执行语句Base *b=new Derived();结果是在堆中创建类Derived的一个对象,并且让类型为Base *的指针 b指向这个对象;其次,执行语句delete b;运行结果是释放b指向对象的内存空间。按照C++的delete操作符的语义,编译器会指针b“对应类型”的对象的析构函数调用(你或许认为是Base::~Base()),但是由于类Base的析构函数声明为虚函数,因此实现方式具有多态特征(具体实现采用的技术请查看相关书籍,例如经典的 Inside The C++ Object Model),因此运行时调用的析构函数是 Derived::~Derived()。第三,子类的析构函数Derived::~Derived()内部会调用父类的析构函数,因此执行结果如上!

基类中的虚析构函数使得派生类在使用多态性时也能被正确的析构。

1.28. c++ string literal的类型是什么?

"abc"的类型为const char [4]

1.29. internal linkage , external linkage & no linkage

http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=41 3.5 Program and linkage

3 A name having namespace scope (3.3.5) has internal linkage if it is the name of

  • an object, reference, function or function template that is explicitly declared static or,
  • an object or reference that is explicitly declared const

and neither explicitly declared extern nor previously declared to have external linkage; or

  • a data member of an anonymous union.

    4 A name having namespace scope has external linkage if it is the name of

  • an object or reference, unless it has internal linkage; or
  • a function, unless it has internal linkage; or
  • a named class (clause 9), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (7.1.3); or
  • a named enumeration (7.2), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (7.1.3); or
  • an enumerator belonging to an enumeration with external linkage; or
  • a template, unless it is a function template that has internal linkage (clause 14); or
  • a namespace (7.3), unless it is declared within an unnamed namespace.

对不同linkage type的符号,ld需要做不同的处理:

  1. external linkage 1.c: extern int a; 2.c: extern int a; 1.c 2.c被编译成目标文件时,1.o, 2.o中对a的引用地址都为0,ld将1.o, 2.o链接以后才能确定a的实际地址
  2. internal linkage 1.c: static int a; 1.c被编译成1.o时,对a的引用地址是a在.data段是的偏移量,1.o被链接后a的实际地址是.data基址+a的偏移量
  3. no linkage 1.c: main () {int a;a=1;} 1.o中a=1中对a的引用地址即是a在.text段中的偏移量,即不需要链接

    1.c:

    static int a;
    static int b;
    int c;
    int d;
    int
    main(int argc, char *argv[]) {
        a=a+1;
        b=b+2;
        c=c+3;
        d=d+4;
        int e;
        e=e+5;
        return 0;
    }
    

    objdump -d 1.o:

    1.o:     file format elf32-i386
    
    Disassembly of section .text:
    
    00000000 <main>:
       0:	8d 4c 24 04          	lea    0x4(%esp),%ecx
       4:	83 e4 f0             	and    $0xfffffff0,%esp
       7:	ff 71 fc             	pushl  -0x4(%ecx)
       a:	55                   	push   %ebp
       b:	89 e5                	mov    %esp,%ebp
       d:	51                   	push   %ecx
       e:	83 ec 10             	sub    $0x10,%esp
      11:	a1 08 00 00 00       	mov    0x8,%eax                 对a的引用地址是a在.data段的offset 0x8,internal linkage
      16:	83 c0 01             	add    $0x1,%eax
      19:	a3 08 00 00 00       	mov    %eax,0x8
      1e:	a1 0c 00 00 00       	mov    0xc,%eax                 对b的引用地址是b在.data段的offset 0xc,internal linkage
      23:	83 c0 02             	add    $0x2,%eax
      26:	a3 0c 00 00 00       	mov    %eax,0xc
      2b:	a1 00 00 00 00       	mov    0x0,%eax                 对c的引用地址是0x0,extern linkage
      30:	83 c0 03             	add    $0x3,%eax
      33:	a3 00 00 00 00       	mov    %eax,0x0
      38:	a1 00 00 00 00       	mov    0x0,%eax                 对d的引用地址是0x0,extern linkage
      3d:	83 c0 04             	add    $0x4,%eax
      40:	a3 00 00 00 00       	mov    %eax,0x0
      45:	83 45 f8 05          	addl   $0x5,-0x8(%ebp)          对e的引用地址是栈上的地址,no linkage
      49:	b8 00 00 00 00       	mov    $0x0,%eax
      4e:	83 c4 10             	add    $0x10,%esp
      51:	59                   	pop    %ecx
      52:	5d                   	pop    %ebp
      53:	8d 61 fc             	lea    -0x4(%ecx),%esp
      56:	c3                   	ret
    

1.30. 临时对象(右值)可被修改?

#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
class Test {
   char * p;
   int val;
public:
   Test (int val) {p=(char *)malloc (10);this->val=val;}
   ~Test () {free (p);p=0;cout<<"dtor called for "<<val<<endl;}
//    Test operator=(const Test & t) {cout<<"calling ="<<endl;memcpy (this->p,t.p,10);cout<<"= done"<<endl;return *this;}
};

Test foo (int val) {return Test (val);}

int
main (int argc, char * argv[]) {
   Test t (1);
   foo (2)=t;
   std::cout<<"here"<<std::endl;
   return 0;
}

foo(2)返回一个Test()临时对象,是一个 右值,但它却是可以调用自己的成员而被修改! 这是C++中类对象做为右值时的特例,对于标准类型如int,C++还是与C兼容的. 临时对象做为右值却可以通过自己的成员函数被修改,这主要是为了方便以下的应用: 临时对象也可以调用成员函数,如 getClassA().getVal(),而不用写成 Class A a=getClassA(), a.getVal()

另外,foo(2)=t时,foo(2)返回的临时对象直到这条语句执行完后才被析构. http://www.9php.com/FAQ/cxsjl/c/2007/11/925528897440.html

1.31. 左值与右值

1.31.1. 函数返回引用

foo & fun() 返回到foo的值用,是左值(当然决不能返回临时对象的引用) 例如:

1.31.2. (void *)(&func_pointer)=…

void *dlsym(void *handle, const char *symbol);
int main(int argc, char **argv) {
void *handle;
double (*cosine)(double);
char *error;

handle = dlopen ("libm.so", RTLD_LAZY);
if (!handle) {
fprintf (stderr, "%s\n", dlerror());
exit(1);
}

dlerror();
//note the following line!
*(void **) (&cosine) = dlsym(handle, "cos");
//it is equal to cosine=(double (*) (double))dlsym(handle,"cos");, using
//(void *)cosine=dlsym(..) is wrong, because (void *)cosine returns a rval
if ((error = dlerror()) != NULL)  {
fprintf (stderr, "%s\n", error);
exit(1);
}

printf ("%f\n", (*cosine)(2.0));
dlclose(handle);
return 0;
}

1.32. when does cast alter the pointer's address

  • 多重继承时
class B1 {};
class B2 {};
class D:public B1,public B2 {};
D * pd=new D();
B1 * pb1=pd; //pb1=pd
B2 * pb2=pd; //pb2!=pd

图:

+-----------------+
| B1 subclass  -  |<-------pd,pb1 point to here
+-----------------+
| B2 subclass     |<-------pb2 point to here
+-----------------+
| D subclass      |
+-----------------+
  • virtual继承时
class B {};
class D:public virtual B {};
D * pd=new D();
B * pb=pd;  //即使是单继承,pb也不等于pd
		    +--pd point to here
图:		 |
		    |
+------------+	 |
|D subclass  |<-----+
|_vptr of D  |--------->+---------------------+
+------------+	     |offset to B subclasso|
|B subclass  |<-----+   +---------------------+
+------------+	 |   |RTTI and other virtua|
     		    |   |l funcs  	           |
     		    |   +---------+-----------+
	       	    |
     		    +--pb point to here,pb equals to pd+_vptr[0]

1.33. c++弱化了data段还是bss段?

和c一样,在c++里,main()之外的全局变量,函数中的static变量,类定义中的static变量都被自动初始化但与c不同的是,如果这些变量是类,会自动调用它们的ctor,看起来是bss被弱化了? 因为这些类不会被自动初始化为0 实际上,是data被弱化了. 这些类被放在bss里,程序执行后,在main()之前,某些函数(如elf中的.init段)会调用它们的ctor来初始化这些类

1.34. 除了ctor,在一个类的所有成员函数中调用虚函数都是通过vptr机制

因为:

  1. test1的构造函数调用fun2时,test2还没建立起来,test1()首次将vptr指向自己的fun2,但test1()中对fun2的调用不通过vptr
  2. 任何时候,通过test1的其他成员函数如fun1调用fun2时,都是通过vptr,因为本质上函数调用都是通过this指针 test1 t; t.fun2() 时编译器可以知道t是确定的类,所以不使用vptr.但当程序进入fun1后,编译器已无法确定fun1(this)里的this到底是什么类型因为成员函数都是通过this指针调用,属于指针调用,所以在成员函数中对虚函数的调用都是通过vptr

1.35. virtual inhereit in c++

class B {int a;};
class D1:public virtual B {}
class D2:public virtual B {}
class E:public D1,public D2 {}
  • memory layout
D1:	         +----------------+
+------+   /-+offset to B,ie,2|
| vptr |---	 +----------------+
+------+   	 |RTTI info of D1 |
| int a|   	 +----------------|----------------------------+
+------+	 |pointer to other virtual funcs if D1 has any |
       	     +---------------------------------------------+


E:
+------------+     	+----------	 --+
| D1 subclass|   /--+ offset to B,ie,3 |
| vptr     	 |---  	|------------------+-----------+
+------------+	| RTTI and other virtual funcs |
| D2 subclass|	+------------------------------+
| vptr     	 |------ similar to vptr of D1, but offset to B is diff, ie,2
+------------+
| E subclass |
+------------+
| B subclass |
| int a    	 |
+------------+
  • why a offset to base class is needed 在D1,D2和E中,都需要在vptr里指定一个到B的offset,虽然B存储的位置就在d1,d2,e附近如果是通过B的派生类(而不是通过派生类指针)使用B的成员,不需要通过vptr指定的offset,因为对于确定的类型,如E,它的内存布局在编译时就确定了, B的成员在E中的位置编译时就已经确定,直接使用即可. 而如果是通过派生类的指针使用B的成员,必须通过vptr指定的offset,例如: D1 * dp=new D1(), B紧接dp D1 * dp=new E(),B和dp之间还间隔了一个sizeof(D2)的距离 D2 * dp2=new E(), B紧接dp2 所以必须通过D1的vptr中指定的offset才能找到B
  • difference when accessing base class member from pointer to derived class or from a derived class (something like access virtual function through pointer or not) http://www.phpcompiler.org/articles/virtualinheritance.html

1.36. c++ pointer-to-member variable and functions

  • what on earth a pointer-to-member pointer 'point' to?
    1. 到类的普通成员函数的指针是函数的真实物理地址
    2. 到类的虚成员函数的指针是虚函数在vtbl中的偏移量
    3. 到类的成员的指针是类成员在类中的偏移量

1.37. c++ inline function

5.cpp
#include <stdio.h>
//typedef void (*fp) ();
inline void fun () {
   //fp tmp=fun;
   printf ("test\n");
}
void f () {
   fun ();
}

6.cpp
//typedef void (*fp) ();
inline void fun ();
int
main(int argc, char *argv[]) {
//    fp tmp=fun;
//    (*tmp)();
   fun ();
   return 0;
}
  • gcc默认不会inline,除非指定-O或-finline g++ 5.cpp -c nm -a 5.o|grep fun 显示 00000000 W _Z3funv g++ 5.cpp -c -O3 或 -finline nm -a 5.o|grep fun 无结果
  • 5.cpp中注释的两行(取inline函数的地址)会阻止compile inline 取消注释后, g++ 5.cpp -c nm -a 5.o|grep fun 显示 00000000 W _Z3funv
  • inline函数和普通函数一样,具有external linkage 如果inline函数通过取函数地址或不指定优化等手段使编译器不Inline它,则目标文件中包含这个函数,如W_Z3funv,这个函数具有external linkage,且这个函数在目标文件中是一个 g++ 5.cpp 6.cpp -O3时,link出错,找不到fun g++ 5.cpp 6.cpp 时,编译成功,main()中调用的是5.cpp中的fun
  • 标准规定,每一个translation unit都要有inline函数的定义

    总结:每个translation unit都应该有inline函数的定义,如将inline函数的定义放在头文件中若不符合这个标准,也有可能编译通过,如将inline函数的定义放在某一个cpp文件中因为inline函数如果没有被inline就和普通函数一样,具有external linkage

1.38. static_cast<Derived *>(virtual base *) is not supported in C++

class A {};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};

来看B是什么样子(A是virtual的,所以放在底部):

+-----+
| B   |
| vptr|
+-----+
| A   |
+--- -+

这里我们假设A和B相差4

再来看D是什么样子(B,C是基类,所以在开头,A是virtual的,所以放在底部):

+------+
| B    |
| vptr |
+--- --+
| C    |
| vptr |
+------+
| D    |
+--- --+
| A    |
+------+

这里A和B就相差12了

所以当compiler看到需要把一个A*转到B*的时候,她并不知道这个offset是4还是12,这个取决于你传过来的对象是B还是D。这就需要一些额外的runtime信息来做这件事。

btw. reinterpret_cast<A*>(B*)是允许的,它假设是第一种情况

1.39. ctor initializer list

ctor initializer list 中执行的顺序取决于类中声明的顺序, 而不是 initializer list 中写的顺序…

Author: [email protected]
Date: 2008-10-06 Mon 00:00
Last updated: 2022-02-06 Sun 21:58

知识共享许可协议