Skip to content

Latest commit

 

History

History
636 lines (486 loc) · 18.6 KB

c_cpp_diff.org

File metadata and controls

636 lines (486 loc) · 18.6 KB

main function

Only int main() or int main(int argc, char **argv) are allowed. The third char **envp parameter is not defined by the C++ standard and should be avoid. Instead, extern char **environ should be declared to provide access to the program’s environment variables. The value of argv[argc] equals nullptr.

The return type is int, not void. return statement may be omitted, and main returns 0 in this case. When a C++ ends normally, dtors of globally defined objects are activated. A function like exit does not normally end a C++ program and is therefore deprecated.

strict type checking

A prototype must be known for each funciton before it is called and the call must match the prototype. return must explicitly return int in main. An empty parameter list indicates the total absence of parameter, i.e. as with void.

Implicit conversions from generic pointers void* to typed pointers are not allowed.

Strongly types enumerations (C++11)

The old style enum has their values not restricted to the enum type name itself, but to the scope where the enumeration is defined. Two enumerations having the same scope cannot have identical names.

enum class solves such problems. The value type used by enum class can be specified instead of int only.

enum class CharEnum : unsigned char {
    NOT_OK,
    
    OK
}

... operator is allowed to show a sequence of symbols of a enum class.

case SafeEnum::Not_OK ... SafeEnum:OK:
    cout << "Status is known\n";

NULL pointer, 0 pointer and nullptr

In C++, all zero values are coded as 0. NULL should be avoided. 0 or NULL pointer may lead to trouble with function overloading since it’s actually int or long, not as a pointer void*. Use nullptr instead since C++11.

In C, a NULL constant is equal to 0 but the value of a pointer variable with NULL value is not necessarily represented as all zero bits. int *p = 0 is equal to int *p = NULL but p is not guaranteed to be zero. This pointer value is equal to 0 or NULL. From C99 standard:

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

Null pointers are equal to 0 or NULL as the latter is converted to the former.

If one operand is a pointer and the other is a null pointer constant, the null pointer constant is converted to the type of the pointer.

Default arguments

default arguments must be known at compile-time (instead of finding these arguments in the implementation at run-time). Therefore, the default arguments must be mentioned at the function’s declaration.

#define __cplusplus

Each C++ compiler which conforms to the ANSI/ISO standard defines the symbol __cplusplus (C++ standard version in use).

Using standard C functions

Normal C functions which are compiled and collected in a run-time library can be used in C++ programs.

extern "C" void c_function(type args) or

extern "C"
{
// C header if needed
// C-declarations here 
}

The combination of the predefined symbol __cplusplus and the possibility to define extern "C" functions offers the ability to create header files for both C and C++.

#ifdef __cplusplus
extern "C"
{
#endif

/* declaration of C-data and functions are inserts here */

#ifdef __cplusplus
}
#endif

The standard C header files are built in this manner and are therefore usable for both C and C++.

Variable Definition inside if, while and switch Since C++98

In C++, local variables can be defined and initialized within if-else, switch, while statements since .

typedef and using

The keyword typedef is not required anymore when defining union, struct or enum defintions.

struct someStruct {
//
}

someStruct whatVar;

The scope of typedefs is restricted to compilation units. Therefore, typedefs are usually embedded in header files which are then included by multiple source files in which the typedefs should be used.

In practice, typedef and using can be used interchangeably.

typedef unsigned long long int FUN(double, int);
using FUN = unsigned long long int (double, int);
using FUN = auto (double, int) -> unsigned long long int;

Evaluation order of operands

  • Expressions using postfix operators (like index operators and member selectors) are evaluated from left to right.
  • Assignment expressions are evaluated from right to left
  • operands of shift operators are evaluated from left to right.

The overloaded operator is evaluated like the built-in operator it overloads.

Attributes

Attributes are used to inform the compiler about situations that are intentional but are by themselves for the compiler to issue warnings. Before standardization, they are commonly defined as compiler extensions.

  • [[fallthrough]]: if falling through is intentional, this attribute should be used so that the compiler does not give a warning.
switch (selector) {
    case 1:
    case 2:
        ...
        [[fallthrough]]; // no warning
    case 3:
        ...
    case 4: // a warning
}
  • [[maybe_unused]]: applied to a class, typedef-name, variable, parameter, non-static data member, a function, an enumeration or an enumerator. No warning is generated when the entity is not used.
  • [[nodiscard]]: specified when declaring a function, class or enumeration. This attribute requires that the return value of a function may be ignored only when explicitly cast to void.
  • [[noreturn]]: used in functions like std::terminate, std::abort.
  • [[deprecated]] / [[deprecated("reason")]]:
int [[nodiscard]] importantInt()

importantInt(); // warning issued

const keyword

const requires the qualified object be non-modifiable, either resulting in a compile-time error (directly) or an undefined behavior (indirectly).

In C++, const may imply a compile-time constant expression, meant as a rvalue and thus by default with internal linkage. Variables declared const can be used to specify the size of an array. Const expressions may be used as case labels, enumerators, initializer expressions or bit-sized fields.

In C, const variables have external linkage by default and they are not constant expressions.

global namespace and scope resolution operator ::

#include <cstdio>

double counter = 50;

int main()
{
    for (int counter = 1; counter != 10; counter++) {
        printf("%d\n", ::counter / counter); //global `counter` divided by local `counter`
    }
}

Stream objects cin, cout, cerr

Some advantages of using streams are:

  • Using insertion and extraction operators is type-safe. Old style functions may be given wrong format specifier. With streams there are no format strings.
  • Insertion and extraction may be extended, allowing objects of classes to be inserted into or extracted form streams.
  • Streams are independent of the media they operate on.

Initializer lists

C++ extends the concept of initializer list by introducing the type initializer_list<Type> where Type is replaced by the type name of the values used in the initializer list.

Initializer lists are recursive, so they can be used with multidimensional arrays, structs and clases.

void values2(std::initializer_list<std::initializer_list<int>> iniValues)
{}
values2({{1, 2}, {2, 3}, {3, 5}, {4, 7}, {5, 11}, {6, 13}});

Implicit conversions are not allowed in initializer lists, making C++11 initializer syntax more type-safe.

Designated initialization

As C++ requires that destruction of data members occurs in the opposite order as their construction it is required that, when using designated initialization, members are initialized in the order in which they are declared in their class or struct. A union can be initialized using designated initialization.

In C++, it is not allowed to reorder the initialization of members in a designated initialization list.

Initializer for bit fields

(C++2a) Bit fields is allowed them to be initialized by default by using initialization expressions in their definitions.

struct FirstIP4word
{
    uint32_t version: 4 = 1; // version now 1, by default
    uint32_t header: 4 = 10; // TCP header length now 10, by default
    uint32_t tos: 8;
    uint32_t length: 16; 
};

Type inference using auto (C++11)

With type inference, the programmer cares more about the behavior of the type in use rather than its formal type.

The keyword auto can be used to simplify type definitions of variables and return types of functions if the compiler is able to determine the proper types of such variables or functions. It is no longer used as a storage class specifier.

Plain types and pointer types are used as-is when declared auto. A reference’s basic type (without the reference, omitting const and volatile) is used. If a reference is required, use auto& or auto&&. Likewise, const and/or pointer specifications can be used in combination with the auto keyword.

The declaration of such a function int (*intArrPtr())[10]; is rather complex. Using auto, it becomes

auto intArrPtr() -> int (*)[10];

which is called a late-specified return type.

(C++14) Late return type specifications are no longer required for functions returning auto, simply

auto autoReturnFunction();

in which case, all return values must have an identical type. Functions merely returning auto cannot be used before the compiler has seen their definitions. So they cannot be used after mere declarations. When such functions are implemented as recursive function, at least one return statement must have been seen before the recursive call.

auto fibonacci(size_t n) 
{
    if (n <=1 )
        return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

Structured binding declarations (C++17)

Usually, when functions need to return mutliple values, a return-by-argument construction is often used. When multiple vlaues should be returned from a function, a struct can be used.

struct Return {
    int first;
    double second;
};

Return fun() 
{
    return { 1, 12.5};
}

Return& fun2()
{
    static Return ret{4, 5};
    return ret;
}

The struct definition can completely be omitted if fun returns a pair or tuple. Instead of referring to the elements of the returned struct, pair or tuple structured binding declarations can also be used. Struct data members are automatically bound to variables.

auto [one, two] = fun();
auto&& [rone, rtwo] = fun();
auto& [lone, ltwo] = fun2();

There doesn’t have to be a function call!

auto const &[lone, ltwo] = Return{4, 5};
auto &&[lone, ltwo] = Return{4, 5};

for (auto &[year, amount, interest] : structArray)
    cout << "Year " << year << ": amount = " << amount << '\n';

Before C++17, one has to use std::tie to achieve similar results only with tuples or pairs. There is no way similar to std::ignore to discard a component with structured binding.

The object doesn’t even have to make its data member publicly available

Range-based for-loops

  • Plain arrays
  • Initializer lists;
  • standard containers
  • any other type offering begin() and end() functions returning iterators.

(C++20) range-based for-loop can have a init-statement.

(C++17) if, switch with init-statement

Before using the condition clauses an initialization clause may be used to define additional variables (plural, as it may contain a comma-separated list of variables, similar to the syntax that’s available for for-statements).

Raw String Literals (C++11)

Raw string literals start with an R, followed by a double quote, optionally followed by a label (which is an arbitrary sequence of characters not equal to (, followed by (. The raw string ends at the closing parenthesis ), followed by the label (if specified when starting the raw string literal), which is in turn followed by a double quote.

The ugly syntax might seem strange at first, but consider Python’s triple quotation syntax, one has to find a way to distinguish quotation marks inside the string from the closing sequence (in Python’s case, the triple quotation cannot appear inside the string content). Custom labels give users a chance to define one’s own closing sequence to allow true raw strings in case something inside the string resembles the predefined closing sequence.

R"label(whatever raw string you want)label"
char const *noPrompt =
R"(
    if (d_debug__)
        s_out__ << '\n';
)";

Character Literal Type

Character literals are considered int in C but of actual character types in C++. This may affect function overloading resolution;

sizeof('a') // larger than 1 in C, but 1 in C++

Binary constants (C++14)

Binary integral constants can be defined using the prefixes 0b or oB. Available in C since C23.

Three-Way Comparison <=> (Spaceship Operator) (C++20)

New language-defined data types

There is a subtle issue to be aware of when converting applications developed for 32-bit architectures to 64-bit architectures. When converting, only long types and pointer types change in size from 32 bits to 64 bits. int remains at 32 bits.

L as a prfix is used to indicate a character string whose elements are wchar_t. p specifies the power in hexadecimal floating point numbers, the exponential part is interpreted as a power of 2.

0x10p2 // 16 * 2^2 = 64

If a function should inform its caller about the success or failure of its task, let the function return a bool value. If the function should return success or various types of errors, let the function return enum values, documenting the situation by its various symbolic constants.

Unicode encoding

C++ supports 8, 16 and 32 bit Unicode encoded strings. Two new data types are introduced: char16_t, char32_t representing UTF-16 and UTF-32 respectively. A char type value fits in a UTF-8 unicode value.

char utf_8[] = u8"This is UTF-8 encoded.";
char16_t utf16[] = u"This is UTF-16 encoded.";
char32_t utf32[] = U"This is UTF-32 encoded.";

char utf_8[] = u8"\u2018";
char16_t utf16[] = u"\u2018";
char32_t utf32[] = U"\u2018";

Casts

C++ prorams should merely use the new style C++ casts as they offer the compiler facilities to verify the sensibility of the cast.

https://stackoverflow.com/questions/573294/when-to-use-reinterpret-cast

https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used

static_cast

The static_cast<type>(expression) is used to convert ‘conceptually comparable or related types’ to each other.

sqrt(static_cast<double>(x) / y);
cout << static_cast<int>(Enum::VALUE);
tolower(static_cast<unsigned char>(ch));

The static_cast is used in the context of class inheritance to convert a pointer to a derived class to a pointer to its base class. Also, use static_cast to convert void * to an intended destination pointer.

const_cast

A const\_cast(expression) expression is used to undo the const attribute of a (pointer) type.

The need for a const_cast may occur in combination with functions from the standard C library which traditionally weren’t always as const-aware as they should.

dynamic_cast

Different from the static_cast, whose actions are completely determined compile-time, the dynamic_cast’s actions are determined run-time to convert a pointer to an object of some class.

reinterpret_cast

reinterpret_cast should only be used when it is known that the information as defined in fact is or can be interpreted as something completely different. Think of the reinterpret_cast as a cast offering a poor-man’s union: the same memory location may be interpreted in completely different ways. Avoid this unless necessary.

reinterpret_cast<pointer type>(pointer expression)
cout.write(reinterpret_cast<char const *>(&value), sizeof(double)); // value is a double variable
bool is_little_endian() {
  std::uint16_t x=0x0001;
  auto p = reinterpret_cast<std::uint8_t*>(&x);
  return *p != 0;
}