Storage classes in C++ a tutorial

 

The storage classes in C++ are : auto , static , extern , mutable , register , and thread_local . They are used in declarations to specify how a definition takes place .

Definition for a variable , is allocating its storage , for a function , it is writing down the instructions that it is going to execute , for the class types : class , struct and union , it is the declaration of their methods and data members .

extern int var_i;
/*Declaration of a variable .*/

float var_f; 
/*Declaration , and definition of
  an object .*/

int fctFoo(int a , int b);
/*Declaration of a function .*/

int fctFoo(int a , int b){
/*Definition of a function .*/
  return a + b ;}

class classFoo;
/*Declaration of a class .*/

class classFoo{
/*Definition of the classFoo .*/
public :
  int a;
  void addTo(int b ); };

auto

auto means that a definition is automatic to its scope . An object is automatically created when its containing block is entered , and is destroyed , when its containing block is exited .

When a local variable is declared , and if no storage class is specified , it has an automatic storage . A Local variable is declared , in the local scope .

int trivial(int arg_i , float arg_f ){
  double var_d = 1 ; 
  return arg_i + arg_f + var_d ;}
/*arg_i , arg_f and var_d are local 
  variables , they are created 
  on the local scope entry and 
  destroyed on its exit .*/

The auto storage class was removed in C++11 , and the auto keyword since C++11 , has another meaning .

extern

extern means that the definition is external to the scope .

The extern keyword cannot be used in a class scope , so it cannot be used with members of a class , struct or a union . It also cannot be used with the parameters of a function , or with the declaration of a class type .

Example of the usage of extern in a local scope :

#include<iostream>

int var_i  = 1 ;
/*var_i , is not declared extern , 
  as such its definition is not 
  external to its scope .*/

float var_f = 5.0f ;


void fctTrivial(){
  extern int var_i;
  /*var_i is declared ,
    in the local scope .
    The definition of 
    var_i is external to 
    the local scope .*/
  std::cout << var_i << std::endl;
  /*Output:
    1 .*/
  std::cout <<var_f << std::endl;
  /*var_f is not declared ,
    in the local scope , it
    is accessible from
    the global scope .
    Output :
    5  .*/ }


int main(void ){
  fctTrivial(); }

Example of the usage of extern in a global scope :

tr_unit1.cpptr_unit2.cppg++ tr_unit1.cpp tr_unit2.cpp
After compilation , the two gotten object files are merged into one .
int var_i = -1;int var_i;var_i is not declared extern in tr_unit1 . Its definition is not external to its scope .
var_i is not declared extern in tr_unit2 , its definition is not external to its scope .
When merging the two object files , one symbol is gotten with two definitions , this leads to duplicate symbol error .
float var_f = -1.0f;extern float var_f = -1.0f;var_f is not declared extern in translation unit one . Its definition is not external to its scope .
var_f is declared extern in translation unit two . It is allowed to define an extern declaration , and var_f is defined in translation unit two .
When merging the two object files , one symbol is gotten with two definitions , this lead to duplicate symbol error .
float var_f = -1.0f;extern float var_f ;var_f is not declared extern in translation unit one , and a definition is provided .
var_f is declared extern in translation unit two , and no definition is provided .
When merging the two files , one symbol is gotten with the definition provided in translation unit one .
extern double var_d = 2.0;extern double var_d = 3.0;var_d is declared extern in both translation unit one and two , and two definitions are provided .
When merging the two object files , one symbol is gotten with two definitions , this leads to duplicate symbol error .
extern double var_d = 2.0;extern double var_d ;var_d is declared extern in both translation unit one , and translation unit two . It is defined only in translation unit one .
When merging the two object files , one symbol is gotten , with the definition provided by translation unit one .
const int var_ci = 55;const int var_ci = 5;var_ci is declared in both tr_unit2 and tr_unit1 . It is declared as constant , and not declared as extern .
When merging the two object files , each has a separate constant , the constant are not merged .
const int var_ci = 55;extern const int var_ci = 5;var_ci is declared in both translation units , as a constant , and defined in both . It is declared in translation unit two as extern .
When merging the two gotten object files , two separate constants are gotten .
const int var_ci = 55;extern const int var_ci;var_ci is declared constant in the two translation units . In translation unit two , it is declared as extern , and no definition is provided .
When merging the two gotten object files , two constants are gotten , the one from translation unit one is defined , and the one from translation unit two has no definition , this leads to the error of symbol not found .
extern const int var_ci = 55;extern const int var_ci = 5;var_ci is declared as const and extern in both translation units .
A definition is provided in both translation units , this leads to an error of duplicate symbol when compiling .
int foo( ){ /*body*/ }extern int foo( );foo is declared in both translation units one and two .
It is defined in translation unit one , and is declared extern in translation unit two .
When merging the two object files , one symbol is gotten for the function foo , having the definition provided by translation unit one .
tr_unit1.cpptr_unit2.cppg++ tr_unit1.cpp tr_unit2.cpp
After compilation , the two gotten object files are merged into one .

Example of using extern in a namespace .

#include<iostream>
namespace nmspc {
  extern int var_i = -1; }

namespace nmspc {
  extern int var_i ;
  /*extern int var_i  = -1;
    This will cause an error
    of var_i being redefined .*/ }

int main(void ){
  std::cout << nmspc::var_i << std::endl;
  /*Output :
    -1 .*/}

static

static means that the definition is static , in other words , fixed to its scope .

The static storage class , can be applied only to variables , to functions , and to anonymous unions , when they are being declared , in a file scope , in a namespace scope , in a class scope , and in a local scope .

Example of usage of static in a local scope :

#include<iostream>

int iterator(){
  static int var_i = -1;
  /*Definition of var_i is to
    be static to its scope . 
    var_i is not to be created
    every time the local block
    is entered , nor is it to be
    destroyed every time the local 
    block is exited  .*/
  return ++var_i; }

int main(void ){
  std::cout << iterator() << std::endl ;
  std::cout << iterator() << std::endl ;
  /*Output :
    0
    1 .*/}

Examples of usage of static in a global scope :

tr_unit1.cpptr_unit2.cppg++ tr_unit1.cpp tr_unit2.cpp
After compilation , the two gotten object files are merged into one .
static int stFct(){/*body*/ }extern int stFct();stFct is declared static in tr_unit1 , its definition is fixed to its scope .
stFct is declared extern in tr_unit2 , its definition is extern to its scope .
When merging the two files , no definition is found for stFct , this will lead to compiler error.
static int var_i = 1 ;int var_i = -1;var_i is declared static in tr_unit1 , its definition is fixed to its scope .
var_i is not declared static in tr_unit2 , its definition is not fixed to its scope .
When merging the two gotten object files , two symbols are gotten , one is fixed to its scope , and the other is not fixed to its scope .
/*File tr_unit1.cpp */

static int id = 0 ;

int generateId(){
  return id++; }

void resetId(){
  id = 0; }

/*File tr_unit2.cpp */

#include<iostream>

extern int generateId();
extern int resetId();

int main(void ){
  std::cout << generateId() << std::endl ;
  std::cout << generateId() << std::endl ;
  resetId();
  std::cout << generateId() << std::endl ; }

/*
$ g++ tr_unit1.cpp tr_unit2.cpp
$ ./a.out
0
1
0
*/

Example of usage of static in a class scope :

#include<iostream>

class Trivial{
public :
  static int var_i;
  static int foo_f(); };

int Trivial::var_i = 0;

int Trivial::foo_f(){
  return Trivial::var_i++; }


int main(void ){
  std::cout << Trivial::var_i << std::endl;
  /*Output : 
    0 */
  std::cout << Trivial::foo_f() << std::endl;
  /*Output : 
    0 */
  std::cout << Trivial::foo_f() << std::endl;
  /*Output : 
    1 */
  std::cout << Trivial::var_i << std::endl; 
  /*Output : 
    2 */ }

mutable

mutable means that the definition is mutable to its scope .

The mutable storage class , applies only to non static class data members .

#include<iostream>

class Trivial{
public:
  int var_i;
  mutable float	var_fm;
  void foo_f() const; 
  /*foo_f is a constant member 
    function .*/ };

void Trivial::foo_f() const {
  /*Provide a definition for foo_f .*/
  var_fm /= 2; }

int main(void ){
  const	Trivial	trivial	= {1 , 1.0f };
  /*define a trivial object .*/

  /*trivial.var_i = 3 ;
    This statement is illegal , because
    the trivial object has been declared 
    const .*/

  trivial.var_fm = -1.0f;
  /*var_fm can be assigned a value ,
    since its storage class is mutable .*/

  std::cout << trivial.var_fm << std::endl ;
  /*Output :
    -1 .*/

  trivial.foo_f();
  /*a function declared const , is not
    allowed to changed an object data
    member , unless this data member
    is declared mutable .*/

  std::cout << trivial.var_fm << std::endl ;
  /*
    Output :
    0.5 .*/ }

register

register means that a definition is to be registered . This is not necessarily done , the register storage class can be ignored by the compiler .

register is done for faster access . The definition can be registered either in cache memory , or in a cpu register .

register only apply to local variables .

The register storage class was deprecated in the C++11 standard , and removed in the c++17 standard .

#include<iostream>

int fly(register int a , register int v ){
  while(v > 0 ){
    a *= a;
    v--; }
  return a; }

int main(void ){
  std::cout << fly(2 , 4 ) << std::endl; 
  /*Output : 
    65536 .*/ }

thread_local

thread_local means that the definition is local to its thread .

This storage class can only be applied to variables declared in a local scope , or in a namespace such as the global namespace , or to static data members .

thread_local means that a variable is local to its thread , the variable is created when its thread is created , and is destroyed when its thread is destroyed .

The thread_local storage class , can be used with the static , and extern storage classes , and it was introduced in C++11 .