Sorry if title is confusing, I couldn't find an easy way to write it in a simple sentence. Anyways, the issue I'm facing:
// header:
class SomeThing
{
private:
SomeThing() {} // <- so users of this class can't come up
// with non-initialized instances, but
// but the implementation can.
int some_data; // <- a few bytes of memory, the default
// constructor SomeThing() doesn't initialize it
public:
SomeThing(blablabla ctor arguments);
static SomeThing getThatThing(blablabla arguments);
static void generateLookupTables();
private:
// declarations of lookup tables
static std::array<SomeThing, 64> lookup_table_0;
static SomeThing lookup_table_1[64];
};
The getThatThing
function is meant to return an instance from a lookup table.
// in the implementation file - definitions of lookup tables
std::array<SomeThing, 64> SomeThing::lookup_table_0; // error
SomeThing Something::lookup_table_1[64]; // <- works fine
I just can't use a std::array
of Something
, unless I add a public ctor SomeThing()
in the class. It works fine with old-style arrays, I can define the array, and fill it up in the SomeThing::generateLookupTables()
function. Apparently the type std::array<SomeThing, 64>
does not have a constructor. Any ideas on how to make it work, or maybe a better structure for this concept?
============= EDIT =======
The friend std::array<SomeThing, 64>
approach seems like a nice idea, but:
It is going to be used in arrays in other places as well. I would like to guarantee this class to always keep certain invariants towards to external users. With this friendly array, a user can accidentally create an uninitialised array of SomeThing
.
Also, the lookup tables are generated using a rather complicated process, can't be done per inline, as in std::array<SomeThing, 64> SomeThing::lookup_table_0(some value)
Solution 1. You can't get an instance of a static class while you can get instances of a class having private constructor via static methods. Private constructor is used, for instance, in the Singleton design pattern (see, for istance "Implementing Singleton in C#"[^]).
A private constructor does not allow a class to be subclassed. A private constructor does not allow to create an object outside the class. If all the constant methods are there in our class we can use a private constructor. If all the methods are static then we can use a private constructor.
A static class can only have a static constructor and public/private does not apply since your code can never call this constructor (the CLR does). So you may not use a access modifier (public/private/...) on a static constructor. Save this answer.
Yes, we can access the private constructor or instantiate a class with private constructor. The java reflection API and the singleton design pattern has heavily utilized concept to access to private constructor.
The std::array<SomeThing, 64>
class clearly doesn't have access to the private
default constructor when it tries to define the instance. You can give it the necessary access by adding
friend class std::array<SomeThing, 64>;
to the definition of SomeThing
.
The solution:
std::array<SomeThing, 64> SomeThing::lookup_table_0 {{ }};
Note: as explained here, {{}}
is required to value-initialize the std::array
without warnings in gcc. = {}
and {}
are correct but gcc warns anyway.
The key to the solution is that some form of initializer must be present.
A terminology check first: all objects are initialized in C++. There are three forms of this, default, value and zero. There are no "non-initialized" objects ; objects with no explicit initializer are called default-initialized. In some circumstances this means the member variables of the object may be indeterminate ("garbage").
What is the problem with the no-initializer version? Firstly, the constructor for std::array<SomeThing, 64>
is defined as deleted because the declaration std::array<SomeThing, 64> x;
would be ill-formed (due to lack of an accessible default constructor for SomeThing
, of course).
That means that any code which attempts to use the default constructor for std::array<SomeThing, 64>
is in turn ill-formed. The definition:
std::array<SomeThing, 64> SomeThing::lookup_table_0;
does attempt to use the default constructor, so it is ill-formed. However once you start introducing initializers, then the default constructor for std::array
is no longer in play; since std::array
is an aggregate then aggregate initialization occurs which bypasses the implicitly-generated constructor(s). (If there were any user-declared constructors then it would no longer be an aggregate).
The version with initializers works because of [dcl.init]/13 (n3936):
An initializer for a static member is in the scope of the member’s class
The definition of list-initialization transforms { }
to { SomeThing() }
here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With