8

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)

5
  • Consider using std::vector if your class is movable. If it's not movable, I think std::deque still works, provided you emplace, of course. Commented Dec 30, 2014 at 1:11
  • I wonder if this class definition might be ill-formed, standard containers should only be instantiated with a complete type. (And this problem only arises due to class containing a static member which comes from a template of itself) Commented Dec 30, 2014 at 2:26
  • Matt: that standard container is not instatiated yet in the header, but only in the implementation. Commented Dec 30, 2014 at 2:38
  • This question is interesting as it is a case where C-style array cannot be so simply replaced by std::array Commented Dec 30, 2014 at 2:57
  • I think I have another semi solution: if C++ works the same way as C in this regard, global variables, such as Something::lookup_table_0 ( which is not in the stack of any function, so global ) are initialized to zeroed out memory. So if I make a private constructor that just sets some_data to zero, and use that to value-initialize the std::array, that is essentially the same thing. But it is still a hack. Commented Dec 30, 2014 at 3:06

3 Answers 3

5

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.

Sign up to request clarification or add additional context in comments.

Comments

4

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.

4 Comments

For me, this results in: " error: missing initializer for member 'std::array<Something, 64ul>::_M_elems' [-Werror=missing-field-initializers] "
That is essentially the same as the other solution lookup_table_0( SomeThing() } . There is a difference between non-initialized, and value-initialized. Anyways, right now I'm thinking I will stay with old-style arrays.
After some thinking, I think I can accept this one as a solution, expecting the compilers optimiser to recognise there is no work to do anyways. BTW, I just tried this constructor: SomeThing() { std::cout << "ctor here\n"; } It prints "ctor here\n" 64 times with your example lookup_table_0 {{ }} but prints it 64 times with a C-style array as well. I was originally looking for the concept of "a chunk of memory with size 64*sizeof(SomeThing)", without running a constructor 64 times, but I guess the result is the same with any decent compiler anyways.
OK. You could allocate a chunk of memory and use placement new on it when you're ready but that is a lot more mess. If your default ctor actually does nothing it'll be optimized out anyway.
4

As your constructor is private, std::array can't use it.

You may add friend class std::array<SomeThing, 64>; in SomeThing to give access to the constructor.

An alternative is to use the available public constructor to initialize the element of array:

std::array<SomeThing, 64> SomeThing::lookup_table_0{
    SomeThing(blablabla_ctor_arguments), ..
};

EDIT:

You can even do, if you have your move or copy constructor available:

std::array<SomeThing, 64> SomeThing::lookup_table_0{ SomeThing() };

to have your whole array default initialized.

9 Comments

Thx, but as I see, that would expose the private constructor. See my EDIT in the question.
@BuellaGábor: It seems that I have found the magic solution :)
Wow, that is clever. I think, it actually creates an instance, leaves it non-initialized with garbage memory, and makes another 63 copies of that garbage memory. Which is kind of stupid, but well, technically "works".
@BuellaGábor SomeThing() calls the default constructor, there is no "non-initialized with garbage memory"
@MattMcNabb : look at the constructor in the question: it does nothing. Meanwhile, the class does actually have member variable ( not mentioned in the question, but it does have ). That member had no constructor, so it is just "garbage" memory as I see it. Actually, I originally wanted 64 instances on garbage memory, i.e. uninitalized memory.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.