Proper setter and getter for static member variable in header-only library
Asked Answered
E

1

6

I have a few small header-only libraries (the header-only part is important). In the initial versions, I had some static members in the classes defined therein. It didn't occur to me until later (when I used them in a bigger project) that the static members would violate the ODR. I wanted to keep them header-only, so defining the static members in a separate .cpp file was out of the question. One well-known solution is to use a Meyers singleton function-local static variable for each static member (as suggested, for example, here).

That is all well and good, but since I want the singleton to behave like a member variable, I want to be able to get and set the value using setters and getters. But what do getters and setters for Meyers singletons function-local static variables look like? I haven't been able to find any solutions to that particular problem.

To clarify, these are the requirements:

  • I want the behaviour of a static member variable inside a header-only library (so I cannot put definitions in a .cpp file)
  • I want a getter which is a getter only (I shouldn't be able to modify the value by assigning to the reference returned by the getter)
  • I also want to be able to modify the value via a dedicated setter.

EDIT 1:

I would like to explain why you might need this.

The static variables in the libraries I mentioned define the default values for some parameters. However, rather than hard-coding these default, I want to give the user the option to set the default values at the beginning of the program so they don't have to pass the values manually each time they call a member function or construct a new instance.

Also, although I agree that the use of the term "Meyers singleton" in the example provided here is misleading (I'm just using an int value), there is nothing stopping you from using this paradigm with custom classes which you only want a single instance of. In such cases, the "Meyers singleton" term would be justified.


EDIT 2:

This has become somewhat irrelevant with the introduction of inline static members in C++17, but I'll leave it up for people who don't have the option to use C++17.

Epicalyx answered 10/8, 2017 at 12:25 Comment(5)
I dont understand how a setter can make sense for a singleton. If it is a singleton then there is no second instance that could be passed to set()Mehta
@tobi303 I think perhaps the "Meyers singleton" pattern is misnamed. OP never said the class itself had to be a singleton. Just that they wanted a static instance of the class and I think the "Meyers singleton" pattern is suitable for that.Spalla
I want the functionality of a static member variable, and the way to implement that is to have a Meyers singleton for that variable so I don't have to define it outside the class. Does that make sense? In my libraries, these static variables define some default values, but I want to be able to be able to set those default values from outside the class.Epicalyx
yes makes sense, though I wouldnt call it Meyers singelton, I mean its the same strategy but there is no singleton hereMehta
@ChrisDrew Yes, except I want a static instance of the variable, not the class itself. And I want to keep the library header-only.Epicalyx
E
9

Solution

#include <iostream>

class Foo
{

    private:

        static int& val()
        {
            static int v = 0;
            return v;
        }

    public:

        Foo()
        {
            set_val(14);
        }

        Foo(const int _v)
        {
            set_val(_v);
        }

        // The setter uses the fact that val()
        // returns a non-const reference,
        // so we can assign to it.
        static void set_val(const int _v)
        {
            val() = _v;
        }           

        // A true getter.
        // Returns const int&, so we cannot assign to it.
        static const int& get_val()
        {
            return val();
        }
};

int main(void)
{

    std::cout << "val is " << Foo::get_val() << "\n";
    Foo f1; // Set the value implicitly via an object constructor
    std::cout << "val is " << Foo::get_val() << "\n";
    Foo f2(5); // Set the value explicitly via an object constructor
    std::cout << "val is " << Foo::get_val() << "\n";
    Foo::set_val(42);
    std::cout << "val is " << Foo::get_val() << "\n";
    // Foo::get_val() = 4; // Doesn't compile, as required

    return 0;
}

Output:

val is 0
val is 14
val is 5
val is 42

Setting the value via constructors can (and probably should) be omitted. I just wanted to show that it can be done. This is a lot of code just for one variable, but not that much more than for a non-static member.

Any thoughts, comments and suggestions welcome!

Epicalyx answered 10/8, 2017 at 12:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.