Variable Scope and Namespaces

Variable Scope

Scope is the range over which a particular variable is defined and can have a value. In C++, scope is defined by code blocks, which are marked by curly braces {}. Function parameter lists are also scoping units, as are certain statements (for, while, if, switch).

In the case of functions, the calling unit may have a variable named x, and a function may also have a variable named x, and if x is not an argument to the function then it will be distinct from the x in the calling unit.

x=20.;
float z=myfunc(x);

etc.

float myfunc(float y) {

float x=10.;  //can declare locals anywhere
return x*y;
}

A variable that is only in scope in a particular block is said to be local to that unit. Variables that are visible from more than one block are global to those blocks.

Global variables are “in scope” within the file where they are declared. To make them visible in other files, they should be placed within a header and declared with the extern keyword.

extern float myglobal;

As a general rule, globals over multiple files should be avoided. Defining them in a class would be preferable. C++20 and up will add modules, which would be the most appropriate structure for this type of use. Modules are familiar to Python and Fortran programmers, to name two languages that support them; they are a programming construct that isolates a unit of code, separating it into a separately compiled bundle, from which other program units can import its variables and functions.

Within a scoping unit, if a local variable is defined with the same name as a global variable, the local variable takes precedence. The global variable can be accessed with the scope resolution operator ::.

#include <iostream>

int j=99.;

int mysub(int, float);

int main() {

    int i=12;
    float x=11.4;

    int j=mysub(i,x);
    std::cout<<"outside function i="<<i<<" j="<<x<<"\n";
    std::cout<<"global j "<<::j<<"\n";

    //code block
    { 
    int i=19;
    std::cout<<"i in block "<<i<<"\n";
    }
    std::cout<<"i outside block "<<i<<"\n";

    return 0;
}

int mysub(int i, float x) {

    x=x+3.9;
    i=int(x);
    std::cout<<"in function i="<<i<<" x="<<x<<" j="<<j<<"\n";

    return i+3;

}

Beware of changes in behavior in for loops. Prior to the C++98 standard, declaring a loop variable within the parentheses left it in scope. So code such as the following was legal:

for (int i=0; i<10; i++)
{
   // code
}

x = (float)i;

Old code tends to persist so programmers may encounter this. The solution is to declare i outside.

int i;
for (i=0; i<10; i++)
{
   // code
}

x = (float)i;

Namespaces

We have seen the scope-resolution operator before:

std::cout<<"Print something\n";

In this case, std is a namespace. A namespace is something like the “family name” of a group of variables and/or functions. The standard namespace std includes all standard C++ functions and data structures and is brought in through standard headers.

A fully qualified name includes all namespace references.

std::vector<std::string> words;

By employing namespaces, different libraries can define functions with the same name. The “family name” namespace can then differentiate them.

Defining a Namespace

You can define your own namespaces. Most usually the elements are classes, which we have not yet discussed, but that is not necessary.

namespace blue {
   float x,y;
}
namespace yellow {
   float x,y;
}

Notice that we can have variables with the same names in different namespaces, because a namespace is a scoping unit. We must reference the variables with their namespace name:

#include <iostream>

namespace blue {
   float x, y;
}

namespace yellow {
   float x, y;
}

int main() {

   blue::x=10.4;
   blue::y=12.8;

   yellow::x=17.0;
   yellow::y=16.11;

   float z=blue::x+yellow::x;
   std::cout<<"Result "<<z<<"\n";

   return 0;
}

Namespaces can be nested:

#include <iostream>

namespace blue {
   float x, y;

   namespace yellow {
       float x, y;
}

}

int main() {

   blue::x=10.4;
   blue::y=12.8;

   blue::yellow::x=17.0;
   blue::yellow::y=16.11;

   float z=blue::x+blue::yellow::x;
   std::cout<<"Result "<<z<<"\n";

   return 0;
}

The Using Directive

When we insert a using statement into a scoping unit, we do not have to preface each member of the namespace with the name. Most commonly we do this with the standard namespace.

#include <iostream>
using namespace std;

int main() {
   
    cout<<"Now we don't need the prefix\n";
    
    return 0;
}

In this case, we declare that we are using namespace std at a global scope through the rest of the file. We can also use namespaces within a scoping unit, and that persists only within that unit.

Another form of the directive limits the reference to one item only. The namespace must have been defined before we can do this.

#include <iostream>
using namespace std;

namespace blue {
   float x, y;
}

namespace yellow {
   float x, y;
}

int main() {

   using blue::x;
   using yellow::y;

   x=10.4;
   blue::y=12.8;

   yellow::x=17.0;
   y=16.11;

   float z;
   z=x+yellow::x;
   std::cout<<"First result "<<z<<"\n";

   z=blue::y+y;
   std::cout<<"First result "<<z<<"\n";

   return 0;
}

Using in Header Files

Introducing a using directive in a header file will bring the namespace into scope and can result in name collisions, especially if multiple headers are used. It is strongly recommended that programmers use fully-qualified names in headers, with the using in any corresponding implementation files.

Previous
Next