ikerhurtado.com
You're in
Iker Hurtado's pro blog
Developer | Entrepreneur | Investor
Software engineer (entrepreneur and investor at times). These days doing performant frontend and graphics on the web platform at Barcelona Supercomputing Center

Relearning C++: Notes on entity scope, visibility and namespaces

6 Mar 2015   |   iker hurtado  
Share on Twitter Share on Google+ Share on Facebook
In this post I write down some notes as from my study of C++ on entity scope, visibility and namespaces

Entity scope and visibility

The scope of a entity determines which parts of the program can reference the entity, its visibility.

Local or block scope

An entity declared within a block has block scope, and is only visible inside that block. Variables with block scope are known as local variables.

Variables in declarations that introduce a block, such as function parameters and variables declared in loops and conditions are local to the block they introduce.

Global scope

Variables declared outside of a block are called global variables. Global variables have program scope, so that they can be accessed everywhere in the program, that is they can be used across multiple files, and they are only destroyed when the program ends.

A variable can only be defined once. Global variables can have two declarations: the defining declaration (or simply definition), which causes memory to be allocated; and the referencing declaration (or simply declaration), which does not allocate memory but links to an existing memory.

extern keyword

In order to use a global variable declared in another file, we have to use a forward declaration or a header file, along with the extern keyword. extern tells the compiler that you are not declaring a new variable, but referring to a variable declared elsewhere.

Global variables should be avoided because they increase the program’s complexity considerably: they can be used anywhere in the entire program and are very difficult to track. Their values can be changed by any function in the program.

const global variables

By default, a global variable has external visibility. However, const global variable has file visibility (as if static specifier is used). As the result, you can place all const global variables in a header file, and include the header in all the source files. To set a const global variable to external visibility, include extern keyword.

More scopes

In addition to basic local and global scopes, there more level of scope:

Function scope: A label can be referenced within the entire function in which it is defined.

Function prototype scope: The optional identifiers defined in the function prototype is confined to the function prototype only. There are not bind to the function definition.

Class scope: Class members (data and function) have class scope and are visible inside the class definition. You cannot access a class member directly outside the class definition, even for public members (which is accessed via the dot operator).

static keyword

The static keyword is has different meanings depending on where it is used. When applied to a variable declared inside a block changes it from default automatic duration to static duration. A static duration variable retains it’s value even after the scope in which it has been created has been exited. They are only created (and initialized) once, and then they are persisted throughout the life of the program. Note that the scope keeps local.

When we apply static to a global variable, it changes the variable from global to file scoped variable. This use is deprecated by the C++ standard. File statics should be avoided and unnamed namespaces used instead (see below in namespaces section).

Static variables are initialized to zero (all its bits set to 0), if no initial values are provided. All elements of static array and structures are initialized to zero too.

A static class member (data or function) belongs to the class, instead of instances; there is one copy shared by all the instances. Hence, it can be referenced directly from the class (Classname::staticMemberName). Like other static entities, this retains its value throughout the program execution.

Function visibility

By default, functions have duration of the entire program and external visibility (can be shared across file). We can use static to confine the function to internal visibility (accessible in this file only).

If a function is declared static in its prototype, the compiler/linker search the current file only for the function definition. Otherwise, it searches all the program files. If the function is not found in all the program files, it then searches the libraries.

There is only one function definition. Each file shall have the function prototype (declaration). Since inline functions are often placed in the header, which will be included in all file, the "one definition rule" makes an exception. However, all copies of inline function shall be identical.

Namespaces

Namespaces allow to group named entities that otherwise would have global scope into narrower scopes (namespace scopes). This allows organizing the elements of programs into different logical scopes referred to by names. In other words, namespaces provide a controlled mechanism for preventing global name collisions partitioning the global namespace.

This facilitate the use of independently developed libraries. Such libraries tend to define a large number of global names, such as classes, functions, and templates.

Any declaration that can appear at global scope can be put into a namespace: classes, variables (with their initializations), functions (with their definitions), templates, and other namespaces. Example:

namespace myfirstnamespace{
...
}
Namespaces may be defined at global scope or inside another namespace. They may not be defined inside a function or a class.

A namespace can be defined in several parts. This fact lets compose a namespace from separate interface and implementation files. Thus, a namespace can be organized in the same way as class and function definitions:

• Namespace members that define classes, and declarations for the functions and objects that are part of the class interface, can be put into header files. These headers can be included by files that use those namespace members.

• The definitions of namespace members can be put in separate source files.

Organizing our namespaces this way also satisfies the requirement that various entities may be defined only once in a program. This requirement applies equally to names defined in a namespace. By separating the interface and implementation, we can ensure that the functions and other names we need are defined only once, but the same declaration will be seen whenever the entity is used.

Namespaces that define multiple, unrelated types should use separate files to represent them.

This program organization gives the developers and the users of our library the needed modularity. Each class is still organized into its own interface and implementation files. A user of one class need not compile names related to the others. We can hide the implementations from our users, while allowing the files to be compiled and linked into one program without causing any compile-time or link-time errors. Developers of the library can work independently on the implementation of each type.

Names defined at global scope are defined inside the global namespace. The global namespace is implicitly declared and exists in every program. Each file that defines entities at global scope (implicitly) adds those names to the global namespace. The scope operator can be used to refer to members of the global namespace.

::memberName  // refers to a member of the global namespace

The C++11 standard introduced a new kind of nested namespace, an inline namespace. Unlike ordinary nested namespaces, names in an inline namespace can be used as if they were direct members of the enclosing namespace.

An unnamed namespace is the keyword namespace followed immediately by a block of declarations delimited by curly braces. Variables defined in an unnamed namespace have static lifetime. Unlike other namespaces, it is local to a particular file and never spans multiple files. Names defined in an unnamed namespace are used directly.

An unnamed namespace may be nested inside another namespace. In that case, the names in it are accessed in the normal way, using the enclosing namespace name.

Using Namespace Members

Namespace aliases

A namespace can have many synonyms, or aliases. All the aliases and the original namespace name can be used interchangeably. Example:

namespace nsexample = namespace_example;

Using declaration

A using declaration introduces one namespace member. It allows us to be very specific regarding which names are used in our programs.

using std::cout;

Names introduced in a using declaration obey normal scope rules: they are visible from the point of the using declaration to the end of the declaration scope. The unqualified name may be used only within this scope and nested. Once the scope ends, the fully qualified name must be used.

A using declaration can appear in global, local, namespace, or class scope. In class scope, such declarations may only refer to a base class member

Using directive

A using directive make all the names from a specific namespace visible without qualification. The short form names can be used from the point of the using directive to the end of the scope in which the using directive appears. A using directive may appear in global, local, or namespace scope, but not in a class scope.

using namespace std;

In general, a namespace might include definitions that cannot appear in a local scope. As a consequence, a using directive is treated as if it appeared in the nearest enclosing namespace scope.

void f()
{
  using namespace A; // injects the names from A into the global scope
}

One place where using directives are useful is in the implementation files of the namespace itself.

When we pass an object of a class type to a function, the compiler searches the namespace in which the argument’s class is defined in addition to the normal scope lookup. This exception also applies for calls that pass pointers or references to a class type. This exception allows nonmember functions that are conceptually part of the interface to a class to be used without requiring a separate using declaration.

POST A COMMENT: