0

[the Environment is QTCreator 4.2.1, the compiler is MinGW 5.8]

I've created a header "header.h" with the following code :

#ifndef HEADER_H
#define HEADER_H

int variable = 0;

#endif // HEADER_H

And I tried to include the header in two .cpp files, getting multiple definition errors.

But than I changed the code like this and mentioned the static variable (int Class::variable;) in one of two .cpp files:

#ifndef HEADER_H
#define HEADER_H

class Class{
public:
    static int variable;
};

#endif // HEADER_H

Errors disappeared. But why can't I include a header twice without errors and use the global variable inside of two .cpp files (And use it directly, not as a static class member)?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Ivan Silkin
  • 381
  • 1
  • 7
  • 15

2 Answers2

1

There are three main reasons that get combined to explain this:

  1. They are two completely different "things". The syntax may look quite the same, but they do two completely different things in C++.
  2. The One Definition Rule
  3. #includeing a header file is logically equivalent, and is exactly equivalent to physically inserting the contents of the header file into whatever #included it.

So, in the first case, with a header file containing int variable = 0;, every translation unit that #includes this file becomes exactly as if

int variable = 0;

Appeared verbatim in every .cpp file that #included it. Exactly the same. And, obviously, that's a One Definition Rule violation (if more than one .cpp has this, and they are linked together). This actually creates an int variable. It defines it. An object called variable pops into existence, out of thin air. Every .cpp file that includes this becomes a proud owner of an int variable. It exists in every .cpp file, and this violates the One Definition Rule.

On the other hand, you have a class declaration, here:

class Class{
public:
    static int variable;
};

This does not define anything. It just declares a Class, that has a static class member. That's it. It does not define any object. It does not pop anything into existence, or materialize anything out of thin air. This just declares a class, and what's inside it. The One Definition Rule does not come into play for the elementary reason that nothing gets defined here.

So, despite very similar syntax, the end result of very similar looking code is quite different, in C++.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • This explains why it is not accepting it as it is... but how would you implement code where you don't have to go through a class? I am having a similar problem where I declare `int variable;` in my .hpp file and would like to then include in different files. I get the multiple definition error, as you explain. Is there a way to use this variable somehow without having to put it inside a class? – Diana Vallverdu Dec 22 '21 at 17:14
  • Yes, there is. Your C++ textbook should have an explanation of how to correctly use the `extern` keyword for this purpose. – Sam Varshavchik Dec 23 '21 at 00:10
1

After going through the same problem and doing some research I'd like to add on to the answer above! There are different ways to have shared variables, and depending on your situation you will want to use a different option.

Global Variable Declaration

The keyword extern before a variable allows you to declare a completely global variable, that can afterwards be used throughout all your code (as long as you include the specific header where this variable is declared). Pros of this is that it is very simple to setup, but cons is that as your code gets more extensive it becomes harder to get track what these global variables mean. For this reason, it is, in general, not recommended to use global variables.

For example:

Header File - header

#ifndef HEADER_H
#define HEADER_H
extern int global_x;

void print_global_x();

#endif

Source 1 - main

#include "header.h"

// since global_x still needs to be defined somewhere,
// we define it (for example) in this source file
int global_x;

int main()
{
    //set global_x here:
    global_x = 5;

    print_global_x();
}

Source 2 - header.cpp

#include <iostream>
#include "header.h"

void print_global_x()
{
    //print global_x here:
    std::cout << global_x << std::endl;
}

(Example taken from this answer)

Using a namespace

Using a global variable can be unclear in some cases. If you use it in many files, you risk forgetting where this variable came from and where it is being edited exactly. On top of that, you may have many global variables and methods, each specific to aspects of your code. Namespaces allow you to specify what each variable is related to. For example:

Header file

#ifndef HEADER_H
#define HEADER_H
#include <iostream>
namespace x_storage
{
    int x;
    void print_x()
    {
        std::cout << x << std::endl;
    }
}

namespace complicated_algorithms
{
    int x;
    void add_10_to_x_and_print()
    {
        x = x + 10;
        std::cout << x << std::endl;
    }
}
#endif

Source 1 - main

#include "header.h"
int main()
{
    x_storage::x = 3; //set x in the x_storage namespace to 3
    complicated_algorithms::x = 3; // set x in complicated_algorithms namespace to 3
    int x = 4; //set x in the global namespace to 4

    x_storage::print_x(); // prints 3
    complicated_algorithms::add_10_to_x_and_print(); // prints 13
    x_storage::print_x(); // prints 3
    std::cout << x << std::endl; //prints 4
}

Class with static elements

Finally, you can have a class where all elements are static. This comes in hand when you want these variables shared across all the code but these variables need to be modified constantly. This way, all the modification happens inside the class. A place where I found this useful was in my testing environment, as I have a few variables that I reuse throughout the test but I want to reset their value at the beginning of each test. Example:

Header file - Counter.h

#ifndef COUNTER_H
#define COUNTER_H
class Counter
{
public:
    static int get_counter();
    static void add_one();
    static void reset();

private: 
    static int counter;

}
#endif

Source 1 - Counter.cc

int Counter::counter = 0; //must always declare even if not set to anything

int Counter::get_counter()
{
    return counter;
}

void Counter::add_one()
{
    counter++;
}
void Counter::reset()
{
    counter = 0;
}

Source 2 - use of Counter

#include "Counter.h"


void method1()
{
    Counter::reset();
    if(first_condition_met()) Counter::add_one();
}

void method2()
{
    if(another_condition_met()) Counter::add_one();
    else Counter::reset();
}

This is a simple code where you can play with the conditions to meet to see how counter behaves given different truth tables. There are other more complicated cases where a similar approach can be very useful.

Diana Vallverdu
  • 321
  • 4
  • 13