0

I've had trouble with setting up variables in namespaces multiple times already, but usually now solve it by having the setup be as follows:

.h

namespace MyNS {
    extern Variable var;
}

.cpp

#include "MyNS.h"
Variable MyNS::var;

Now, this works fine with primitives, but becomes a problem when the Variable object is somewhat of a more complex object.

Namely, my problem this time is that I would like to leave some variables uninitialized until I call a certain function. Something as follows:

.h

namespace MyNS {
    extern Variable var;
    void init();
}

.cpp

#include "MyNS.h"
Variable MyNS::var;
void MyNS::init() { var = Variable(a, b, c); }

This gives a compile time error, because Variable does not have a default constructor (And I don't want it to have one). But when I remove the 2nd line in the .cpp, I get linker error unresolved external symbol.

How can I solve this problem? How do I initialize a variable of a namespace "later"?

The "hacky" solution I have so far is to have my namespace hold a Variable*, initialize it to nullptr, and then assign it the actual object in my init function. But that seems incredibly hacky for something so simple, since I have no actual reason for using pointers in this case.

Fly
  • 810
  • 2
  • 9
  • 28
  • 1
    "*`Variable` does not have a default constructor (And I don't want it to have one)*" - then you can't create instances of it without passing values into its constructor, eg: `Variable MyNS::var(0,0,0);`. Otherwise, you will have to make `var` be a `Variant*` pointer instead, and then `init()` can `new` it. – Remy Lebeau Apr 06 '21 at 22:07
  • @RemyLebeau That's what I have currently, but that's so hacky! What exactly is so special about a namespace that something this simple isn't possible? Or what about my understanding of C++ in general is so wrong that I'm trying to attempt this? – Fly Apr 06 '21 at 22:08
  • This has nothing to do with namespaces at all. This is simply how object construction works in general. If a class has no default constructor then you simply can't create an instance of it without supplying the required values to its constructor, or using [Aggregate Initialzation](https://en.cppreference.com/w/cpp/language/aggregate_initialization). – Remy Lebeau Apr 06 '21 at 22:09
  • Or `std::make_unique(a, b, c)` it. Could be some advantages in a smart pointer if you need to make sure `Variable`'s destructor runs. If its life-time is the whole program, who really gives a crap if it technically leaks. – user4581301 Apr 06 '21 at 22:10
  • How about defining it with initialization, like `Variable MyNS::var(a, b, c);`? – Some programmer dude Apr 06 '21 at 22:10
  • @Someprogrammerdude and where do you think those `a,b,c` values are coming from before `init()` is called? – Remy Lebeau Apr 06 '21 at 22:12
  • @RemyLebeau But why does it *have* to be always initialized? Maybe it's because I come from a Java background, but in other languages there's nothing wrong with having variables be uninitialized until you decide to initialize them. Like, in this case, I simply "don't know" the arguments I want to use for initialization "yet". – Fly Apr 06 '21 at 22:13
  • @user4581301 that is just a smart wrapper for the "hacky" solution the OP wants to avoid – Remy Lebeau Apr 06 '21 at 22:13
  • @Fly "*why does it have to be always initialized?*" - because that is just the way objects work. You are declaring an instance of a class, so it has to be constructed at the point of instantiation. The only other solution would be to make `var` be a `std::optional`. In Java, all objects are reference types, you have a `null` reference until you point it at an created object. In C++ terms, you do the same thing using pointers. – Remy Lebeau Apr 06 '21 at 22:13
  • @user4581301 Of course, but at that point it's a technicality - I'm still using a pointer (Without actually needing to semantically have one) – Fly Apr 06 '21 at 22:13
  • How about using something like `Variable MyNS::var(0, 0, 0);` to initialize the variable? – R Sahu Apr 06 '21 at 22:15
  • 1
    Fly, you could have a Lazy-loaded `static` variable hidden inside a function inside another wrapper similar to a [Meyer's Singleton](https://stackoverflow.com/a/1008289/4581301), but that's not much better. – user4581301 Apr 06 '21 at 22:16
  • @RSahu Not quite possible in a "good" way I think: To make it more concrete, my object accesses the file system (It's a OGL shader, I'm giving it the shader files), so it would be quite weird to pass it some dummy file just to get around this problem – Fly Apr 06 '21 at 22:18
  • @RemyLebeau I don't know, but they don't seem to be passed along to the `init` function through arguments. Without a proper [mcve] it's really impossible for any of us to do more than give helpful hints. – Some programmer dude Apr 06 '21 at 22:20
  • @Someprogrammerdude Yeah sorry about that I thought that it would be simpler for everyone to have the code as light as possible. In reality, I'm creating shaders so I have to create them with information on file locations, and the shader then passes data on to the GPU. I don't want to just have all that done for the whole lifetime of my program, which is why I want to initialize it "later". – Fly Apr 06 '21 at 22:22
  • 1
    Then (smart) pointers are probably going to be the best solution. – Some programmer dude Apr 06 '21 at 22:24

1 Answers1

1

Variable does not have a default constructor (And I don't want it to have one)

Then you simply can't create an instances of Variable without passing values into its constructor, eg:

namespace MyNS {
    extern Variable var;
    void init();
}
#include "MyNS.h"

Variable MyNS::var(0, 0, 0);

void MyNS::init() { var = Variable(a, b, c); }

Another solution is to make var be a Variable* pointer instead, and then init() can new it, eg:

namespace MyNS {
    extern Variable* var;
    void init();
    void cleanup();
}
#include "MyNS.h"

Variable* MyNS::var = nullptr;

void MyNS::init() { var = new Variable(a, b, c); }
void MyNS::cleanup() { delete var; }

Alternatively:

#include <memory>

namespace MyNS {
    extern std::unique_ptr<Variable> var;
    void init();
}
#include "MyNS.h"

std::unique_ptr<Variable> MyNS::var;

void MyNS::init() { var = std::make_unique<Variable>(a, b, c); }

Another solution would be to make var be a std::optional<Variable> instead, eg:

#include <optional>

namespace MyNS {
    extern std::optional<Variable> var;
    void init();
}
#include "MyNS.h"

std::optional<Variable> MyNS::var;

void MyNS::init() { var = Variable(a, b, c); }

Another solution is to wrap var inside of a singleton, eg:

namespace MyNS {
    struct VariableAccess {
        static Variable& Var();
    };
}
#include "MyNS.h"

Variable& MyNS::VariableAccess::Var() {
    static Variable var(a, b, c);
    return var;
};
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770