C/C++标准的一些摘录

C/C++的很多资料网上数不胜数,但是经常会遇到看了一些文章资料后,我觉得作者自己都没彻底明白到底写的是什么(回头看看我以前写的文章也是,那时的眼光太片面和浅显了)。
所以对于C/C++的东西我觉得还是要直接来翻标准文档才行,因为标准是不会出现歧义的。不能盲目地只是在网上搜寻并相信别人二次消化过的资料。
我认为对于C/C++语言特性的知识,查阅这四份文档就足够了(点击即可在线预览或下载):

之所以C语言标准没有依据最新的C11标准是因为目前的C++标准(C++14)的Normative references的C部分是ISO/IEC 9899:1999,使用TCPL和TC++PL可以作为C/C++标准的应用性描述,可以相互印证。
更多的关于C++Normative references的内容可参照ISO/IEC 14882:2014(E) §1.2 Normative references.
我会逐渐把一些常见的会让人觉得模棱两可的语言特性查阅的标准规范摘录到这里来,可以保证写出的东西在标准文档中都有依据。

exit() behavior

function prototype:

1
2
// The attribute-token noreturn specifies that a function does not return.
[[noreturn]] void exit(int status)

The function exit() has additional behavior in this International Standard:

  • First, objects with thread storage duration and associated with the current thread are destroyed.Next, objects with static storage duration are destroyed and functions registered by calling atexit
    are called.See 3.6.3 for the order of destructions and calls. (Automatic objects are not destroyed as a result of calling exit().)
    If control leaves a registered function called by exit because the function does not provide a handler for a thrown exception, std::terminate() shall be called (15.5.1).
  • Next, all open C streams (as mediated by the function signatures declared in) with unwritten buffered data are flushed, all open C streams are closed, and all files created by calling tmpfile() are removed.
  • Finally, control is returned to the host environment. If status is zero or EXIT_SUCCESS, an implementation-defined form of the status successful termination is returned. If status is EXIT_FAILURE, an implementation-defined form of the status unsuccessful termination is returned.Otherwise the status returned is implementation-defined.

return statement

A function returns to its caller by the return statement.
A return statement with neither an expression nor a braced-init-list can be used only in functions that do not return a value, that is, a function with the return type cv void, a constructor (12.1), or a destructor (12.4).
A return statement with an expression of non-void type can be used only in functions returning a value; the value of the expression is returned to the caller of the function. The value of the expression is implicitly converted to the return type of the function in which it appears. A return statement can involve the construction and copy or move of a temporary object (12.2). [ Note: A copy or move operation associated with a return statement may be elided or considered as an rvalue for the purpose of overload resolution in selecting a constructor (12.8). — end note ]A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.

1
2
3
std::pair<std::string,int> f(const char* p, int x) {
return {p,x};
}

A return statement with an expression of type void can be used only in functions with a return type of cv void; the expression is evaluated just before the function returns to its caller.

Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.(注意:main函数不遵从这个规则)
由于C++中main函数的返回值是用作std::exit的参数的,所以标准特地规定,在main函数末尾不显式返回,程序执行效果等价于return 0;
A return statement in main has the effect of leaving the main function (destroying any objects with automatic storage duration) and calling std::exit with the return value as the argument. If control reaches the end of main without encountering a return statement, the effect is that of executing

1
return 0;

关于main函数的原型和返回值有更详细的文章:关于main函数的原型和返回值

non-deduced context

The non-deduced contexts are:

  • The nested-name-specifier of a type that was specified using a qualified-id.
  • The expression of a decltype-specifier.
  • A non-type template argument or an array bound in which a subexpression references a template parameter.
  • A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.
  • A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (13.4), and one or more of the following apply:
    • more than one function matches the function parameter type (resulting in an ambiguous deduc-tion), or
    • no function matches the function parameter type, or
    • the set of functions supplied as an argument contains one or more function templates.
  • A function parameter for which the associated argument is an initializer list (8.5.4) but the parameter does not have std::initializer_list or reference to possibly cv-qualified std::initializer_list type.

    1
    2
    template<class T> void g(T);
    g({1,2,3}); // error: no argument deduced for T
  • A function parameter pack that does not occur at the end of the parameter-declaration-list.

构造和析构时抛出异常

An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution.

对象析构时的执行顺序

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members, the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 12.6.2). A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction (see 12.6).

对象构造时的执行顺序

In a non-delegating constructor, initialization proceeds in the following order:

  • First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes,where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
  • Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list(regardless of the order of the mem-initializers).
  • Then, non-static data members are initialized in the order they were declared in the class definition(again regardless of the order of the mem-initializers).
  • Finally, the compound-statement of the constructor body is executed.

[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed inthe reverse order of initialization. - end note ]

类的内置类型数据成员初始化

若没有显式提供构造函数初始化该成员,则其值是未定义的。--在标准12.6.2 Initializing bases and members(P268)

Note: An abstract class (10.4) is never a most derived class, thus its constructors never initialize virtual
base classes, therefore the corresponding mem-initializers may be omitted. -end note ] An attempt to
initialize more than one non-static data member of a union renders the program ill-formed. [Note: After the call to a constructor for class X for an object with automatic or dynamic storage duration has completed, if the constructor was not invoked as part of value-initialization and a member of X is neither initialized nor given a value during execution of the compound-statement of the body of the constructor, the member has an indeterminate value. -end note

1
2
3
4
5
6
7
8
9
10
11
12
13
struct A {
A();
};
struct B {
B(int);
};
struct C {
C() { } // initializes members as follows:
A a; // OK: calls A::A()
const B b; // error: B has no default constructor
int i; // OK: i has indeterminate value
int j = 5; // OK: j has the value 5
};

If a given non-static data member has both a brace-or-equal-initializer and a mem-initializer, the initializa-tion specified by the mem-initializer is performed, and the non-static data member’s brace-or-equal-initializer is ignored. [ Example: Given

1
2
3
4
5
struct A {
int i = /* some integer expression with side effects */ ;
A(int arg) : i(arg) { }
// ...
};

the A(int) constructor will simply initialize i to the value of arg, and the side effects in i’s brace-or-equal-initializer will not take place. - end example ]

Undefined Behavior

behavior for which this International Standard imposes no requirements[ Note: Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed.- end note ]

求值顺序

C++没有明确规定表达式中子表达式的求值顺序。你不能假设表达式是按照从左到右的顺序求值的。

1
2
// 到底是先调用f()还是g()并没有明确的规定
int x=f(2)+g(3);

还有下面这样的代码:

1
2
int i=1;
v[i]=i++; // UB

其中的赋值行为有可能执行为v[1]=1,也可能执行v[2]=1;

闭包(closure)

直接贴标准吧...
ISOIEC 14882 2014(C++14) §5.1.2 p90

The evaluation of a lambda-expression results in a prvalue temporary (12.2). This temporary is called the closure object. A lambda-expression shall not appear in an unevaluated operand (Clause 5), in a template-argument, in an alias-declaration, in a typedef declaration, or in the declaration of a function or function template outside its function body and default arguments. [ Note: The intention is to prevent lambdas from appearing in a signature. - end note ] [ Note: A closure object behaves like a function object (20.9). - endnote ]
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type - called the closure type - whose properties are described below. This class type is neither an aggregate (8.5.1) nor a literal type (3.9). The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [ Note: This determines the set of namespaces and classes associated with the closure type (3.4.2). The parameter types of a lambda-declarator do not affect these associated namespaces and classes. - end note ] An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing:

  • the size and/or alignment of the closure type,
  • whether the closure type is trivially copyable (Clause 9),
  • whether the closure type is a standard-layout class (Clause 9), or
  • whether the closure type is a POD class (Clause 9).

Lvalue and rvalue

Expressions are categorized according to the taxonomy in Figure.

lvalueAndRvalue

  • An lvalue (so called, historically, because lvalues could appear on the left-hand side of an assignment expression) designates a function or an object. [ Example: If E is an expression of pointer type, then *E is an lvalue expression referring to the object or function to which E points. As another example,the result of calling a function whose return type is an lvalue reference is an lvalue. - end example ]
  • An xvalue (an “eXpiring” value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved, for example). An xvalue is the result of certain kinds of expressions involving rvalue references (8.3.2). [ Example: The result of calling a function whose return type is an rvalue reference is an xvalue. - end example ]
  • A glvalue (“generalized” lvalue) is an lvalue or an xvalue.
  • An rvalue (so called, historically, because rvalues could appear on the right-hand side of an assignment expression) is an xvalue, a temporary object (12.2) or subobject thereof, or a value that is not associated with an object.
  • A prvalue (“pure” rvalue) is an rvalue that is not an xvalue. [ Example: The result of calling a function whose return type is not a reference is a prvalue. The value of a literal such as 12, 7.3e5, or true is also a prvalue. - end example ]

Every expression belongs to exactly one of the fundamental classifications in this taxonomy: lvalue,xvalue, or prvalue. This property of an expression is called its value category. [ Note: The discussion of each built-in operator in Clause 5 indicates the category of the value it yields and the value categories of the operands it expects. For example, the built-in assignment operators expect that the left operand is an lvalue and that the right operand is a prvalue and yield an lvalue as the result. User-defined operators are functions, and the categories of values they expect and yield are determined by their parameter and return types. - end note ]

{}构造不允许int->double的原因

因为如果int和double所占的位数一样,则其中的某些int向double转换必将损失信息。

It sometimes comes as a surprise that {}-construction doesn’t allow int to double conversion, but if (as is not uncommon) the size of an int is the same as the size of a double, then some such conver-sions must lose information. Consider:

1
2
3
4
static_assert(sizeof(int)==siz eof(double),"unexpected sizes");
int x = numeric_limits<int>::max(); //largest possible integer
double d = x;
int y = x;

We will not get x==y. Howev er, we can still initialize a double with an integer literal that can be represented exactly.

char带不带符号由实现定义

It is implementation-defined whether a plain char is considered signed or unsigned. --[TCPL 6.2.3.1]

[14882:2014(E) § 3.9.1]:
A char, a signed char, and an unsigned char occupy the same amount of storage and have the same alignment requirements.
In any particular implementation, a plain char object can take on either the same values as a signed char or an unsigned char; which one is implementation-defined.

For each value i of type unsigned char in the range 0 to 255 inclusive, there exists a value j of type char such that the result of an integral conversion (4.7) from i to char is j, and the result of an integral conversion from j to unsigned char is i.

默认参数的几个反例

Default arguments for a member function of a class template shall be specified on the initial declaration of the member function within the class template.

1
2
3
4
5
6
7
8
9
10
class C {
void f(int i = 3);
void g(int i, int j = 99);
};

void C::f(int i = 3) { // error: default argument already
} // specified in class scope

void C::g(int i = 88, int j) { // in this translation unit,
} // C::g can be called with no argument

Local variables shall not be used in a default argument. Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
void f() {
int i;
extern void g(int=0); // yes
extern void g(int=i); // error
g();
// ...
}
void g(void){
cout<<"g(void)"<<endl;
}
void g(int x){
cout<<"g(int)\t"<<x<<endl;
}

The keyword this shall not be used in a default argument of a member function.

1
2
3
class A {
void f(A* p = this) { } // error
};

更多内容请查看ISO/IEC 14882:2014(E) § 8.3.6

对象的几种初始化方式

zero-initialize

To zero-initialize an object or reference of type T means:

  • if T is a scalar type (3.9), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;
  • if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
  • if T is a (possibly cv-qualified) union type, the object’s first non-static named data member is zero-initialized and padding is initialized to zero bits;
  • if T is an array type, each element is zero-initialized;
  • if T is a reference type, no initialization is performed.

default-initialize

To default-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9), the default constructor (12.1) for T is called (and the initialization is ill-formed if T has no default constructor or overload resolution (13.3) results in an ambiguity or in a function that is deleted or inaccessible from the context of the initialization);
  • if T is an array type, each element is default-initialized;
  • otherwise, no initialization is performed.

If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.

value-initialize

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;
  • if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
  • if T is an array type, then each element is value-initialized;
  • otherwise, the object is zero-initialized.

An object that is value-initialized is deemed to be constructed and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed,”etc., even if no constructor is invoked for the object’s initialization.

Initialization of non-local variables

There are two broad classes of named non-local variables: those with static storage duration (3.7.1) and those with thread storage duration (3.7.2). Non-local variables with static storage duration are initialized as a consequence of program initiation. Non-local variables with thread storage duration are initialized as a consequence of thread execution. Within each of these phases of initiation, initialization occurs as follows.
Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place. A constant initializer for an object o is an expression that is a constant expression, except that it may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types [ Note: such a class may have a non-trivial destructor - end note ]. Constant initialization is performed:

  • if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (5.19) and the reference is bound to an lvalue designating an object with static storage duration, to a temporary (see 12.2), or to a function;
  • if an object with static or thread storage duration is initialized by a constructor call, and if the initialization full-expression is a constant initializer for the object;
  • if an object with static or thread storage duration is not initialized by a constructor call and if either the object is value-initialized or every full-expression that appears in its initializer is a constant expression.

Together, zero-initialization and constant initialization are called static initialization; all other initial-ization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. Dynamic initialization of a non-local variable with static storage duration is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initializa-tion. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other non-local variables with static storage duration have ordered initialization.
Variables with ordered initialization defined within a single translation unit shall be initialized in the order of their definitions in the translation unit. If a program starts a thread (30.3), the subsequent initialization of a variable is unsequenced with respect to the initialization of a variable defined in a different translation unit. Otherwise, the initialization of a variable is indeterminately sequenced with respect to the initialization of a variable defined in a different translation unit. If a program starts a thread, the subsequent unordered initialization of a variable is unsequenced with respect to every other dynamic initialization. Otherwise,the unordered initialization of a variable is indeterminately sequenced with respect to every other dynamic initialization. [ Note: This definition permits initialization of a sequence of ordered variables concurrently with another sequence. - end note ] [ Note: The initialization of local static variables is described in 6.7.- end note ]

value and/or reference semantics

What is value and/or reference semantics, and which is best in C++?

With reference semantics, assignment is a pointer-copy (i.e., a reference). Value (or “copy”) semantics mean assignment copies the value, not just the pointer. C++ gives you the choice: use the assignment operator to copy the value (copy/value semantics), or use a pointer-copy to copy a pointer (reference semantics). C++ allows you to override the assignment operator to do anything your heart desires, however the default (and most common) choice is to copy the value.

Pros of reference semantics: flexibility and dynamic binding (you get dynamic binding in C++ only when you pass by pointer or pass by reference, not when you pass by value).

Pros of value semantics: speed. “Speed” seems like an odd benefit for a feature that requires an object (vs. a pointer) to be copied, but the fact of the matter is that one usually accesses an object more than one copies the object, so the cost of the occasional copies is (usually) more than offset by the benefit of having an actual object rather than a pointer to an object.

There are three cases when you have an actual object as opposed to a pointer to an object: local objects, global/static objects, and fully contained member objects in a class. The most important of these is the last (“composition”).

More info about copy-vs-reference semantics is given in the next FAQs. Please read them all to get a balanced perspective. The first few have intentionally been slanted toward value semantics, so if you only read the first few of the following FAQs, you’ll get a warped perspective.

Assignment has other issues (e.g., shallow vs. deep copy) which are not covered here.

Increment and decrement(prefix and postfix)

1 The user-defined function called operator++ implements the prefix and postfix ++ operator. If this function is a member function with no parameters, or a non-member function with one parameter, it defines the prefix increment operator ++ for objects of that type. If the function is a member function with one parameter (which shall be of type int) or a non-member function with two parameters (the second of which shall be of type int), it defines the postfix increment operator ++ for objects of that type. When the postfix increment is called as a result of using the ++ operator, the int argument will have value zero.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct X {
X& operator++(); // prefix ++a
X operator++(int); // postfix a++
};
struct Y { };

Y& operator++(Y&); // prefix ++b
Y operator++(Y&, int); // postfix b++

void f(X a, Y b) {
++a; // a.operator++();
a++; // a.operator++(0);
++b; // operator++(b);
b++; // operator++(b, 0);
a.operator++(); // explicit call: like ++a;
a.operator++(0); // explicit call: like a++;
operator++(b); // explicit call: like ++b;
operator++(b, 0); // explicit call: like b++;
}

The prefix and postfix decrement operators -- are handled analogously.

Argument-dependent name lookup(ADL)

When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope
friend function or function template declarations (11.3) not otherwise visible may be found. These modifications to the search depend on the types of the arguments (and for template template arguments, the
namespace of the template argument).

1
2
3
4
5
6
7
8
9
10
namespace N {
struct S { };
void f(S);
}
void g() {
N::S s;
f(s); // OK: calls N::f
(f)(s); // error: N::f not considered; parentheses
// prevent argument-dependent lookup
}

For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes is determined entirely by the types of the function arguments (and the namespace of any template template argument).Typedef names and using-declarations used to specify the types do not contribute to this set. The sets of namespaces and classes are determined in the following way:

  • If T is a fundamental type, its associated sets of namespaces and classes are both empty.
  • If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classes. Furthermore, if T is a class template specialization,its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces of which any template template arguments are members; and the classes
    of which any member templates used as template template arguments are members. [ Note: Non-type template arguments do not contribute to the set of associated namespaces. — end note ]
  • If T is an enumeration type, its associated namespace is the innermost enclosing namespace of its declaration. If it is a class member, its associated class is the member’s class; else it has no associated
    class.
  • If T is a pointer to U or an array of U, its associated namespaces and classes are those associated with U.
  • If T is a function type, its associated namespaces and classes are those associated with the function parameter types and those associated with the return type.
  • If T is a pointer to a member function of a class X, its associated namespaces and classes are those associated with the function parameter types and return type, together with those associated with X.

If an associated namespace is an inline namespace (7.3.1), its enclosing namespace is also included in the set. If an associated namespace directly contains inline namespaces, those inline namespaces are also included in the set. In addition, if the argument is the name or address of a set of overloaded functions and/or function templates, its associated classes and namespaces are the union of those associated with each of the members of the set, i.e., the classes and namespaces associated with its parameter types and return type. Additionally, if the aforementioned set of overloaded functions is named with a template-id,its associated classes and namespaces also include those of its type template-arguments and its template template-arguments.

Let X be the lookup set produced by unqualified lookup (3.4.1) and let Y be the lookup set produced by
argument dependent lookup (defined as follows). If X contains:

  • a declaration of a class member, or
  • a block-scope function declaration that is not a using-declaration, or
  • a declaration that is neither a function or a function template

then Y is empty. Otherwise Y is the set of declarations found in the namespaces associated with the
argument types as described below. The set of declarations found by the lookup of the name is the union of X and Y . [ Note: The namespaces and classes associated with the argument types can include namespaces and classes already considered by the ordinary unqualified lookup.

1
2
3
4
5
6
7
8
9
10
11
12
namespace NS {
class T { };
void f(T);
void g(T, int);
}
NS::T parm;
void g(NS::T, float);
int main() {
f(parm); // OK: calls NS::f
extern void g(NS::T, float);
g(parm, 1); // OK: calls g(NS::T, float)
}

When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2) except that:

  • Any using-directives in the associated namespace are ignored.
  • Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.3).
  • All names except those of (possibly overloaded) functions and function templates are ignored.

sizeof(non-member-class)

Complete objects and member subobjects of class type shall have nonzero size.(Base class subobjects are not so constrained.)

1
2
3
class A{};

sizeof(A); // return value is 1.

当我们使用C++的编译器编译上面的代码时,sizeof(A)的结果是1.

派生类数组转换为基类指针后的算数运算

考虑下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct A{
virtual void func(){
cout<<"A"<<endl;
}
};
struct B:public A{
int x{12};
virtual void func()override{
cout<<"B"<<endl;
}
};
void callDerviedFunc(A* x,int index){
for(int i=0;i<index;++i){
// runtime error(Undefined behavior)
(x+i)->func();
}
}
int main(){
B a[10];
callDerviedFunc(a, 10);
}

上面的代码产生为定义行为(一般是运行时错误)。
因为B由A派生而来,而且添加了自己的成员,所以sizeof(B)>sizeof(A),假定sizeof(A)为8,sizeof(B)为16,当将B类型指针转换为A类型指针时,指针的数值运算是指针类型的偏移值,即A类型指针a+1相当于在a指向的地址往后偏移8个字节,而B类型指针b+1等于在b的地址上往后偏移16个字节。所以在callDerviedFunc中只有第一次(x+0)->func()会调用成功,当再一次循环时对(x+1)->func()时,因为在该指针期望的地方找不到虚函数指针所以会产生为定义行为。
标准中是这么规定的:

In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type.——ISO/IEC 14882:2014(E)§5.7 P120

在可能使用一组派生类指针到基类指针转换的时候应该优先选用标准库容器,而不是原生数组,因为原生数组并不像容器那样能够提供类型安全。

copy assignment operator

A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&.——ISO/IEC 14882:2014(E) §12.8 P276

[ Note:An overloaded assignment operator must be declared to have only one parameter; see 13.5.3. — end note ]
[ Note: More than one form of copy assignment operator may be declared for a class. — end note ]
[ Note:If a class X only has a copy assignment operator with a parameter of type X&, an expression of type const X cannot be assigned to an object of type X.

1
2
3
4
5
6
7
8
9
struct X {
X();
X& operator=(X&);
};
const X cx;
X x;
void f() {
x = cx; // error: X::operator=(X&) cannot assign cx into x
}

不要在基类构造函数中调用虚函数期待多态行为

不能期望在基类的构造函数中实现函数的多态行为,因为基类的构造早于派生类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct A
{
A(){func();}
virtual void func(){cout<<"A::func"<<endl;}
};
struct B:public A
{
B(){func();}
void func()override{cout<<"B::func"<<endl;}
};
int main(int argc,char* argv[])
{
A *obj=new B;
delete obj;
return 0;
}
// output
A::func
B::func

lifetimes of objects

We can classify objects based on their lifetimes:

  • Automatic: Unless the programmer specifies otherwise (§12.1.8, §16.2.12), an object declared in a function is created when its definition is encountered and destroyed when its name goes out of scope. Such objects are sometimes called automatic objects. In a typical implementation, automatic objects are allocated on the stack; each call of the function gets its own stack frame to hold its automatic objects.
  • Static: Objects declared in global or namespace scope (§6.3.4) and statics declared in func- tions (§12.1.8) or classes (§16.2.12) are created and initialized once (only) and ‘‘live’’ until the program terminates (§15.4.3). Such objects are called static objects. A static object has the same address throughout the life of a program execution. Static objects can cause seri- ous problems in a multi-threaded program because they are shared among all threads and typically require locking to avoid data races (§5.3.1, §42.3).
  • Free store: Using the new and delete operators, we can create objects whose lifetimes are controlled directly (§11.2).
  • Temporary objects (e.g., intermediate results in a computation or an object used to hold a value for a reference to const argument): their lifetime is determined by their use. If they are bound to a reference, their lifetime is that of the reference; otherwise, they ‘‘live’’ until the end of the full expression of which they are part. A full expression is an expression that is not part of another expression. Typically, temporary objects are automatic.
  • Thread-local objects; that is, objects declared thread_local (§42.2.8): such objects are cre- ated when their thread is and destroyed when their thread is. Static and automatic are traditionally referred to as storage classes.

Array elements and nonstatic class members have their lifetimes determined by the object of which they are part.

给临时对象续命之const T&

当我们声明一个const T&来引用到一个临时对象时,将会给临时对象续命到该引用的生命周期:

1
2
3
4
5
6
{
int x=11,y=12;
const int& z=x+y;
// x+y产生的临时对象将在离开z的lifetime时被销毁。
int e=13;
}// destroy x,y,z,e

销毁该const T&的顺序按照普通的对象销毁顺序(倒序)。
如上面的代码的销毁顺序为e,z,y,x.

标准定义如下:

1
2
3
4
5
6
7
8
9
struct S {
S();
S(int);
friend S operator+(const S&, const S&);
~S();
};
S obj1;
const S& cr = S(16)+S(23);
S obj2;

the expression S(16) + S(23) creates three temporaries: a first temporary T1 to hold the result of the expression S(16), a second temporary T2 to hold the result of the expression S(23), and a third temporary T3 to hold the result of the addition of these two expressions. The temporary T3 is then bound to the reference cr. It is unspecified whether T1 or T2 is created first. On an implementation where T1 is created before T2, T2 shall be destroyed before T1. The temporaries T1 and T2 are bound to the reference parameters of operator+; these temporaries are destroyed at the end of the full-expression containing the call to operator+. The temporary T3 bound to the reference cr is destroyed at the end of cr’s lifetime, that is, at the end of the program. In addition, the order in which T3 is destroyed takes into account the destruction order of other objects with static storage duration. That is, because obj1 is constructed before T3, and T3 is constructed before obj2, obj2 shall be destroyed before T3, and T3 shall be destroyed before obj1.

Point of dealaration

1
2
3
4
5
6
7
#include <iostream>

int main() {
void * p = &p;
std::cout << bool(p);
}
resault is 1

The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any)

1
2
3
unsigned char x = 12;
// Here the second x is initialized with its own (indeterminate) value.
{ unsigned char x = x; }

Note: a name from an outer scope remains visible up to the point of declaration of the name that hides it.

1
2
3
const int i = 2;
// declares a block-scope array of two integers.
{ int i[i]; }

The point of declaration for an enumerator is immediately after its enumerator-definition.

1
2
3
const int x = 12;
// Here, the enumerator x is initialized with the value of the constant x, namely 12.
{ enum { x = x }; }

After the point of declaration of a class member, the member name can be looked up in the scope of its class. [ Note: this is true even if the class is an incomplete class.]

1
2
3
4
struct X {
enum E { z = 16 };
int b[X::z]; // OK
};

declaration and define

A declaration is a definition unless it declares a function without specifying the function’s body (8.4), it contains the extern specifier (7.1.1) or a linkage-specification 25 (7.5) and neither an initializer nor a function-body, it declares a static data member in a class definition (9.2, 9.4), it is a class name declaration (9.1), it is an opaque-enum-declaration (7.2), it is a template-parameter (14.1), it is a parameter-declaration (8.3.5) in a function declarator that is not the declarator of a function-definition, or it is a typedef declaration (7.1.3), an alias-declaration (7.1.3), a using-declaration (7.3.3), a static_assert-declaration (Clause 7), an attribute- declaration (Clause 7), an empty-declaration (Clause 7), or a using-directive (7.3.4).

template default arguments

A template-parameter of a template template-parameter is permitted to have a default template-argument.When such default arguments are specified, they apply to the template template-parameter in the scope of the template template-parameter.

1
2
3
4
5
6
7
8
9
10
11
using namespace std;
template<class T = int> struct A {
static int x;
};

template<> struct A<double> { }; // specialize for T == double
template<> struct A<> {static const int X=111;}; // specialize for T == int

// A<>::X == A<int>::X
// output: 111
cout<<A<>::X<<endl;

static object initialized of C

If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static storage duration is not initialized explicitly,then:

  1. if it has pointer type, it is initialized to a null pointer;
  2. if it has arithmetic type, it is initialized to (positive or unsigned) zero;
  3. if it is an aggregate, every member is initialized (recursively) according to these rules;
  4. if it is a union, the first named member is initialized (recursively) according to these rules.

compound literal of C

ISO/IEC 9899:1999 (E)
A postfix expression that consists of a parenthesized type name followed by a brace-enclosed list of initializers is a compound literal. It provides an unnamed object whose value is given by the initializer list.

1
int *p=(int[]){1,2};

[[noreturn]] attribute

The attribute-token noreturn specifies that a function does not return. It shall appear at most once in each attribute-list and no attribute-argument-clause shall be present. The attribute may be applied to the declarator-id in a function declaration.
The first declaration of a function shall specify the noreturn attribute if any declaration of that function specifies the noreturn attribute. If a function is declared with the noreturn attribute in one translation unit and the same function is declared without the noreturn attribute in another translation unit, the program is ill-formed; no diagnostic required.
If a function f is called where f was previously declared with the noreturn attribute and f eventually returns, the behavior is undefined. [ Note: The function may terminate by throwing an exception. — end note ] [ Note: Implementations are encouraged to issue a warning if a function marked [[noreturn]] might return. — end note ]

1
2
3
4
5
6
7
8
[[ noreturn ]] void f() {
throw "error"; // OK
}
// behavior is undefined if called with an argument <= 0
[[ noreturn ]] void q(int i) {
if (i > 0)
throw "positive";
}

five ways of exiting a function

A return-statement is one of five ways of exiting a function:

  1. Executing a return-statement.
  2. "Falling off the end" of a function; that is, simply reaching the end of the function body.This is allowed only in functions that are not declared to return a value (i.e., void functions) and in main(), where falling off the end indicates successful completion.
  3. Throwing an exception that isn’t caught locally.
  4. Terminating because an exception was thrown and not caught locally in a noexcept function.
  5. Directly or indirectly invoking a system function that doesn’t return (e.g., exit();).

A function that does not return normally (i.e., through a return or "falling off the end") can be marked [[noreturn]]

整型/浮点字面值的类型

简单地说,在未指明后缀的情况下整型字面值(integer literal)可以为int/long int/long long int;

StuffixDecimal literal
noneint/long int/long long int
u or Uunsigned int/unsigned long int/unsigned long long int
l or Llong int/long long int

浮点型字面值为double:

[C++11]The type of a floating literal is double unless explicitly specified by a suffix. The suffixes f and F specify float, the suffixes l and L specify long double.

StuffixDecimal literal
nonedoubld
f or Ffloat
l or Llong double

C和C++中的要求一致。

C和C++中sizeof计算期的区别

一般情况下C语言的sizeof操作也是编译期行为,但是由于C语言具有VLA(variable length array),所以在其操作对象为VLA数组时,会在运行时求值。

[C99]If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.

1
2
3
4
5
size_t fsize3(int n)
{
char b[n+3]; // variable length array
return sizeof b; // execution time sizeof
}

而C++的sizeofsizeof...完全是编译期的行为。

[C++11]The result of sizeof and sizeof... is a constant of type std::size_t.

C和C++的关系/逻辑运算符结果的区别

说白了就是C语言的关系/相等/逻辑以及三目运算符的first-expression的结果均是int(0/1),而C++的均是bool类型。

Relational operators

[C++11]The operands shall have arithmetic, enumeration, or pointer type. The operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) all yield false or true. The type of the result is bool.
[C99]Each of the operators < (less than), > (greater than), <= (less than or equal to), and >=(greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.)The result has type int.

Equality operators

[C++11]The == (equal to) and the != (not equal to) operators group left-to-right. The operands shall have arithmetic,enumeration, pointer, or pointer to member type, or type std::nullptr_t. The operators == and != both yield true or false, i.e., a result of type bool.
[C99]The == (equal to) and != (not equal to) operators are analogous to the relational operators except for their lower precedence.)Each of the operators yields 1 if the specified relation is true and 0 if it is false. The result has type int.

Conditional expressions

[C++11]Conditional expressions group right-to-left. The first expression is contextually converted to bool.It is evaluated and if it is true, the result of the conditional expression is the value of the second expression,otherwise that of the third expression. Only one of the second and third expressions is evaluated.
[C99]The first operand is evaluated; there is a sequence point after its evaluation. The second operand is evaluated only if the first compares unequal to 0; the third operand is evaluated only if the first compares equal to 0;

Logical operator

[C++11]The ||/&& operator groups left-to-right. The operands are both contextually converted to bool.The result is true if both operands are true and false otherwise.
[C99]The ||/&& operator shall yield 1 if both of its operands compare unequal to 0; otherwise, it yields 0. The result has type int.

delete一个非new-expressions创建的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct A
{
A(){cout<<"A::counstructor"<<endl;}
~A(){cout<<"A::destructor"<<endl;}
// AObj conversion to A*
operator A*(){
cout<<"A::convertionToA*"<<endl;
return this;
}
};

A Aobj;
// implicitly converted to a pointer to object type.
delete Aobj;
// equivalent to
delete &Aobj;

If of class type, the operand is contextually implicitly converted (Clause 4) to a pointer to object type.

delete一个非new-expressions创建的对象的行为是未定义的

In the first alternative (delete object), the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (1.8) representing a base class of such an object (Clause 10). If not, the behavior is undefined.

C Library的内存管理函数不会调用new/delete

The functions calloc(), malloc(), and realloc() do not attempt to allocate storage by calling ::operator new().
The function free() does not attempt to deallocate storage by calling ::operator delete().

如果非要用malloc来分配内存的话可以用placement new:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct A
{
A(int z):x(z){cout<<"A::constructor"<<endl;}
~A(){cout<<"A::destructor"<<endl;}
int x;
};
int main(void)
{
A *x=(A*)malloc(sizeof(A));
::new(x) A(11);
cout<<x->x<<endl;
free(x);
return 0;
}
/* output
A::constructor
11
*/

数组名不是指针

数组下标访问背后隐含的逻辑中提到了数组名并不是指向数组中首个元素的地址。

1
int x[3][5];

Here x is a 3 × 5 array of integers. When x appears in an expression, it is converted to a pointer to (the first of three) five-membered arrays of integers. In the expression x[i] which is equivalent to *(x+i), x is first converted to a pointer as described;then x+i is converted to the type of x, which involves multiplying i by the length of the object to which the pointer points, namely five integer objects.

C++ and ISO C

C++11标准中的兼容特性

野指针(Wild Pointer)和悬垂指针(Dangling Pointer)

野指针(Wild Pointer):是指未被初始化(no-initializer)的指针。

ISO/IEC 14882:2014(E)
When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced (5.17).
After the declaration of an uninitialized pointer x (as with int* x;), x must always be assumed to have a singular value of a pointer.

可以看到,未对指针作初始化其值是未定义的。
悬垂指针(Dangling Pointer):是指曾经指向有意义的地址,但此地址的内存已经被释放掉(被操作系统回收)。

1
2
3
4
int *ivalp=new int(11);
delete ivalp;
// 对ivalp执行delete之后ivalp依旧指向原来的地址,此时ival就叫做悬垂指针。
// 对悬垂指针进行操作结果是未定义的,因为此块内存操作系统已经回收(虽然有时候对其操作仍旧是原来的值)。

ISO/IEC 9899:2011(E)
If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.

trivial default constructor

A default constructor is trivial if it is not user-provided and if:

  1. its class has no virtual functions (10.3) and no virtual base classes (10.1), and
  2. no non-static data member of its class has a brace-or-equal-initializer, and
  3. all the direct base classes of its class have trivial default constructors, and
  4. for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.

Otherwise, the default constructor is non-trivial.

原始字符串字面值常量(raw string literal)

以下两个表达式的结果一样。

1
2
string s="\\w\\\\w";
string s1=R"(\w\\w)";

在原始字符串字面值常量中,反斜线就是反斜线,双引号就是双引号。不会被转义。
通常被使用在正则表达式中。
另外,"()"并不是唯一的分隔符,在"(...)"的框架中我们还可以在(之前和)之后加入其他分隔符。
规则要求:符号)之后的字符序列和)前面的序列完全一致。

1
2
R"***("THE STRING IS A TEST")***";
// 等价于"THE STRING IS A TEST"

还有,在原始字符串字面值常量中允许出现换行,是真正的换行而不是换行符:

1
2
3
4
5
string s=R"("123
456
789")";
// 等价于
string s1={"123\n456\n789\n"};

更多内容可以参照《C++程序设计语言》p5.5/p154

放置语法(placement)

如果我们想把对象放置在别的地方,可以提供一个含有额外实参的分配函数。

1
2
3
4
5
6
7
8
9
10
11
class X{
public:
X(int);
// ...
};

void operator new(size_t,void *p){return p;}
// 一个明确的地址
void buf=reinterpret_cast<void*>(0xF00F);
// 在buf处构建X,调用operator new(sizeof(X),buf)
void* p2=new(buf) X;

更多内容参照:
what is this syntax - new (this) T(); [duplicate]
Using new (this) to reuse constructors

override函数不会覆盖其原有默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

struct A {
virtual void foo (int a = 1) {
std::cout << "A" << a;
}
};

struct B : A {
virtual void foo (int a = 2) {
std::cout << "B" << a;
}
};

int main () {
A *b = new B;
b->foo();
}
// output
B1

因为overrideing的函数不会覆盖其默认参数,所以在类B中定义的foo其默认参数仍然为1.

A virtual function call (10.3) uses the default arguments in the declaration of the virtual function determined by the static type of the pointer or reference denoting the object. An overriding function in a derived class does not acquire default arguments from the function it overrides.

static是线程安全初始化的

[ISO/IEC 14882:2014(E) §6.8 P137]

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
The implementation must not introduce any deadlock around execution of the initializer.

static对象实际上是一个线程安全初始化的全局变量的糖。

non-local static的初始化

[ISO/IEC 14882:2014 §3.6.2.4]It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main.

意思就是非局部static存储期变量的动态初始化在main函数的第一个语句执行之前完成(实现定义)。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct A{
A(){cout<<"A::constructor"<<endl;}
};
struct B{
B(){cout<<"B::constructor"<<endl;}
};
static A a;
int main(){
B b;
}
// resault
A::constructor
B::constructor

函数参数的求值顺序

1
2
3
4
5
6
7
8
9
10
int i;

void f(int x) {
std::cout << x << i;
}

int main() {
i = 3;
f(i++);
}

C++标准规定,函数实参的副作用求值在进入函数之前被排序。

[ISO/IEC 14882:2014 §5.2.2.8] All side effects of argument evaluations are sequenced before the function is entered.

所以上面代码的执行结果是34.
也可以从IR代码的角度来看一下编译器对这个特性的实现:

1
2
3
4
5
6
7
8
define i32 @main() #4 {
store i32 3, i32* @i, align 4
%1 = load i32, i32* @i, align 4
%2 = add nsw i32 %1, 1
store i32 %2, i32* @i, align 4
call void @_Z1fi(i32 %1)
ret i32 0
}

可以看到,当我们在执行f(i++)的时候,编译器先将i自增,然后将未自增之前的值(%1)作为实参传递给f。而进入函数f后,因为static对象i的值已经在主函数中被修改(%2),所以,在函数f中输出的值分别是3和4.

VLA不能具有初始化

[ISO/IEC 9899:1999]The type of the entity to be initialized shall be an array of unknown size or an object type that is not a variable length array type.

所以下面这样写是不对的:

1
2
3
int ivlan;
scanf("%d",&ivlan);
int ivla[ivlan]={0}; // Error

正确的做法是在运行时确定数组大小之后对其逐个成员赋值,可以使用for循环或者memset:

1
2
3
4
int fuck;
scanf("%d",&fuck);
int ival[fuck];
memset((void*)ival,0,(unsigned long long)(fuck*sizeof(int)));

template出现在./->以及::的右侧

A name prefixed by the keyword template shall be a template-id or the name shall refer to a class template. [ Note: The keyword template may not be applied to non-template members of class templates. - end note ] [ Note: As is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the nested-name-specifier or the expression on the left of the -> or . is not dependent on a template-parameter, or the use does not appear in the scope of a template. - end note ]

1
2
3
4
5
6
7
8
9
template <class T> struct A {
void f(int);
template <class U> void f(U);
};
template <class T> void f(T t) {
A<T> a;
a.template f<>(t); // OK: calls template
a.template f(t); // error: not a template-id
}

注意要与typename区分,typename是出现时被限定的名字之前,而template则紧挨在模板名之前。

What is Object in C++ Standard?

[ISO/IEC 14882:2014]An object is a region of storage. [ Note: A function is not an object, regardless of whether or not it occupies storage in the way that objects do. - end note ] An object is created by a definition (3.1), by a new-expression (5.3.4) or by the implementation (12.2) when needed. The properties of an object are determined when the object is created.

多态在C++标准(C++14)中的描述

What is polymorphic?

Some objects are polymorphic (10.3); the implementation generates information associated with each such object that makes it possible to determine that object’s type during program execution.

What is polymorphic class?

Virtual functions support dynamic binding and object-oriented programming. A class that declares or inherits a virtual function is called a polymorphic class.

What is polymorphic behavior?

A base class subobject might have a polymorphic behavior (12.7) different from the polymorphic behavior of a most derived object of the same type.

标准中并没有指定编译器应该用何种方式来实现多态,所以多态实现的方式还是依赖于编译器的实现。而大部分编译器都是通过维护一个虚函数表计算偏移实现的,如果代码中涉及直接对虚函数表的访问,那么代码几乎不具有可移植性(跨编译器),所以你不能够假定所有的编译器对虚函数表的实现方式都相同(比如在多重继承中是否合并虚函数表)。

class object size

类类型的完整对象和成员子对象应具有非零大小。

Complete objects and member subobjects of class type shall have nonzero size.(Base class subobjects are not so constrained.)

类对象的基类子对象能够具有零大小。

A base class subobject may be of zero size.

以及对其sizeof的标准描述:

The size of a most derived class shall be greater than zero (1.8).The result of applying sizeof to a base class subobject is the size of the base class type. When applied to an array, the result is the total number of bytes in the array. This implies that the size of an array of n elements is n times the size of an element.

引用不是指针

之前在篇文章中看到作者分析了一下引用在编译器中实现的方式,然后信誓旦旦地说引用就是指针。这是不对的。
首先,看一下g++/clang++是怎么实现的:

1
2
3
int x=1234;
int &y=x;
int *z=&x;

然后查看上面代码的LLVM-IR代码(已省略不相关的部分):

1
2
3
4
5
6
7
%6 = alloca i32, align 4
%7 = alloca i32*, align 8
%8 = alloca i32*, align 8

store i32 1234, i32* %6, align 4
store i32* %6, i32** %7, align 8
store i32* %6, i32** %8, align 8

可以看到,引用在经过编译器之后和指针是一样的。但是这并不意味着引用就是指针。。

[ISO/IEC 14882:2014]A reference can be thought of as a name of an object.

还是那句话:C++中没有要求如何实现的特性都依赖于编译器实现,因此说引用就是指针有失偏颇。

this的类型

[ISO/IEC 14882:2014]The type of this in a member function of a class X is X. If the member function is declared const, the type of this is const X, if the member function is declared volatile, the type of this is volatile X, and if the member function is declared const volatile, the type of this is const volatile X.

我翻阅了C++98/03/11/14的C++标准,发现这些版本的标准之间均没有什么区别。

typedef与using的区别

使用typedefusing都能够定义一个类型别名,但是为什么会有功能如此相似的两个关键字呢?
对于using而言,它不仅仅可以引入别名,还可以引入一个名字空间到name lookup的范围。
从声明一个alias的角度来看,他们两者最重要的区别在于:能否定义模板别名。typedef能实现的using也都可以实现,反之则不行,我觉得C++中之所以保留typedef关键字的主要原因是要与C兼容
来看C++标准中对于typedef的限制部分描述:

[ISO/IEC 14882:2014]It has the same semantics as if it were introduced by the typedef specifier. In particular, it does not define a new type and it shall not appear in the type-id.

想要使用模板别名(template alias),只能用using

1
2
3
4
5
6
7
8
9
template<typename T>
struct A{};

template<typename T>
using VectorA=std::vector<A<T>>;

VectorA<int> x;
// 等价于
std::vector<A<int>> x;

dynamic_cast转换失败

通过dynamic_cast转换指针和引用在转换失败时具有不同的行为,若使用指针转换失败时,会返回一个空指针,若使用引用转换失败时会抛出一个std::bad_cast异常,C++标准中的详细描述:

[ISO/IEC 14882:2014]The value of a failed cast to pointer type is the null pointer value of the required result type. A failed cast to reference type throws an exception (15.1) of a type that would match a handler (15.3) of type std::bad_cast (18.7.2).

implementation-defined behavior

[ISO/IEC 14882:2014]中的Index of implementation-defined behavior:

名字隐藏(name hiding)

1
2
3
4
5
6
7
8
9
struct Astruct{
int x;
};
int Astruct=123;

int main()
{
::Astruct;
}

上面的代码有两个问题:

  1. 在上面的代码中在global scope中使用标识符Astruct使用的标识符是类Astruct还是int对象Astruct?
  2. 在main函数中使用global scope的::Astruct,那么使用的究竟标识符是类Astruct还是int对象Astruct呢?

首先第一个问题的是,在global中使用标识符Astruct是类类型标识符:

A class name (9.1) or enumeration name (7.2) can be hidden by the name of a variable, data member, function, or enumerator declared in the same scope. If a class or enumeration name and a variable, data member, function, or enumerator are declared in the same scope (in any order) with the same name, the class or enumeration name is hidden wherever the variable, data member, function, or enumerator name is visible.

这表示在相同的scope中,类名字会隐藏所有的同名标识符。

而第二个问题:在main函数中使用的::Astruct是int对象Astruct,如果想要使用类名字Astruct则需要显式指定:

1
struct ::Astruct Aobj;

A class declaration introduces the class name into the scope where it is declared and hides any class, variable, function, or other declaration of that name in an enclosing scope (3.3). If a class name is declared in a scope where a variable, function, or enumerator of the same name is also declared, then when both declarations are in scope, the class can be referred to only using an elaborated-type-specifier (3.4.4).

抽象基类的几种实现方式

这里抽象基类的概念为abstract classes,C++标准里是这么描述的:

An abstract class can also be used to define an interface for which derived classes provide a variety of implementations.
An abstract class is a class that can be used only as a base class of some other class; no objects of an abstract class can be created except as subobjects of a class derived from it. A class is abstract if it has at least one pure virtual function. [ Note: Such a function might be inherited: see below. — end note ]
A class is abstract if it contains or inherits at least one pure virtual function for which the final overrider is pure virtual.

这里有两个概念:

  1. 没有抽象类的对象能够创建,除非作为派生类的子对象
  2. 带有最少一个纯虚函数

纯虚函数并非是创建抽象类的唯一的方法,只要能够实现对象不能被创建,也可以算作抽象类。
可以通过将构造函数定义为protected来实现(因为派生类的构造函数会调用基类的构造函数,所以我们要确保派生类具有访问权限):

1
2
3
4
5
struct A{
protected:
A(){}
A(const A&){}
};

当然这种方法太粗暴了。
同样用这种方法也可以实现以下两种操作(飞继承层次下可以定义为private):

  • 只能在栈区分配对象,将operator newoperator delete成员函数定义为protected。
  • 只能在堆区分配对象,将destructor成员函数定义为protected。

如果实在没有需要将成员函数定义为纯虚函数的理由,也可以把析构函数定义为纯虚函数:

1
2
3
4
struct A{
virtual ~A()=0;
};
A::~A{}

其实这样并不是一个好主意,因为派生类的析构函数会隐式调用基类的析构函数,所以当我们将一个析构函数定义为纯虚函数时,就必须为它提供一个实现,否则就会产生一个链接错误,但是这就违背了纯虚函数的定义:

A function declaration cannot provide both a pure-specifier and a definition.

1
2
3
struct C {
virtual void f() = 0 { }; // ill-formed
};

所以,在C++中实现一个行为的方式有很多种,但是不是每一种都是最好的,为了适合自己的需求。

指针比较的行为

Comparing pointers to objects is defined as follows:

  • If two pointers point to different elements of the same array, or to subobjects thereof, the pointer to the element with the higher subscript compares greater.
  • If one pointer points to an element of an array, or to a subobject thereof, and another pointer points one past the last element of the array, the latter pointer compares greater.
  • If two pointers point to different non-static data members of the same object, or to subobjects of such members, recursively, the pointer to the later declared member compares greater provided the two members have the same access control (Clause 11) and provided their class is not a union.

subobjects in C++

[ISO/IEC 14882:2014]Objects can contain other objects, called subobjects. A subobject can be a member subobject (9.2), a base class subobject (Clause 10), or an array element.

从这个角度来看如果想要在C中实现继承数据成员,只需要在struct derived中包含一个struct base成员即可。
使用C来尝试实现OO的特性,很有意思,这段时间分析一下写篇文章出来。

全文完,若有不足之处请评论指正。
本文标题:C/C++标准的一些摘录
文章作者:ZhaLiPeng
发布时间:2016年11月12日 01时19分
更新时间:2017年04月05日 15时19分
本文字数:本文一共有12.6k字
原始链接:https://imzlp.me/posts/19242/
许可协议: CC BY-NC-SA 4.0
转载请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!