Can I place "non-static blocks" of code in class definitions?
Asked Answered
D

7

9

Is there non-static block in C++?

If no, how to emulate it elegantly?

I want to replace something like :-

class C{
    public: void ini(){/* some code */}
};
class D{
    std::vector<C*> regis; //will ini(); later
    public: C field1; 
    public: C field2;  
    public: C field3;             //whenever I add a new field, I have to ... #1
    public: D(){
        regis.push_back(&field1);
        regis.push_back(&field2);
        regis.push_back(&field3); //#1 ... also add a line here
    }
    public: void ini(){
        for(auto ele:regis){
            ele->ini();
        }
    }
};

with :-

class D{
    std::vector<C*> regis;                        
    public: C field1;{regis.push_back(&field1);}//less error-prone (because it is on-site)
    public: C field2;{regis.push_back(&field2);}
    public: C field3;{regis.push_back(&field3);}
    public: D(){    }  //<-- empty
    public: void ini(){
        for(auto ele:regis){
            ele->ini();
        }
    }
};

I found many questions related to static-block in C++, but didn't found any one about non-static-block.

To make it easy to answer, here is a full code.

It can be done using X-MACRO (wiki link), but I am trying to avoid it.

Edit

In real case, fieldX can has any types that derived from a certain C.

I consider another bad workaround :-

class D{
    std::vector<C*> regis;     
    char f(C& c){   regis.push_back(&c); return 42;}                 
    public: C field1; char dummyWaste1=f(field1);
    public: C field2; char dummyWaste2=f(field2);
    public: C field3; char dummyWaste3=f(field3);

Edit2 (bounty reason)

skypjack's answer is very useful, but I am curious to find out more alternatives.
The final objective is to emulate general non-static block that has more variety.
In other words, it would be nice if new solution can solve this :-

class D{
    int field1=5;
    { do something very custom; /* may access field1 which must = 5 */}
    //^ have to be executed after "field1=5;" but before "field2=7"
    int field2=7;
    int field3=8;
    { do something very custom ; /* e.g. "field1=field2+field3" */}
    //^ have to be executed after "field3=8;"
};

without wasting 1 char (or more - for alignment) for each block.

Darondarooge answered 27/4, 2017 at 5:34 Comment(6)
Do you really want a vector of pointers? Wouldn't a vector of references be better by stating the memory ownership?Mcvay
@Mcvay Yes, I can, but the problem is still not solved by doing so.Darondarooge
Why do you need the individual fields?Flyer
@Flyer Each field are a database. I am trying to do a database hub. stackoverflow.com/a/43557157 I also want to access those fields by name for readability. Ex. field1,2,3 real name are DatabaseHenToCage1to1, DatabaseChickToFood1to1.Darondarooge
@Darondarooge that's why I wrote a comment... by the way, as long as they have all the same type, why do you need to have them as separate variables? Couldn't you just make an initialized vector of the fields: std::vector<C> regis = { C(), C(), C() }; EDIT: or make it a map, thus keeping the names readable...Mcvay
@Mcvay I guess I have to create some (semi-)enum to query the field, and use it like d->getField(D::ENUM_FIELD1); It would work, but in some situation, it seems to be less suitable/flexible ... quite subjective.Darondarooge
V
6

how to emulate it elegantly?

You can initialize regis directly:

std::vector<C*> regis = { &field1, &field2, &field3 };

That is, define your class as:

class D{
public:
    C field1;
    C field2;
    C field3;

    void ini(){
        for(auto ele:regis){
            ele->ini();
        }
    }

private:
    std::vector<C*> regis = { &field1, &field2, &field3 };
};

Otherwise, if you can add a constructor to C, revert the logic and have it adding itself to the vector:

#include<vector>

struct C {
    C(std::vector<C*> &vec) {
        vec.push_back(this);
        // ...
    }

    void ini() {}
};

class D{
    std::vector<C*> regis{};

public:
    C field1 = regis;
    C field2 = regis;
    C field3 = regis;

    void ini(){
        for(auto ele:regis){
            ele->ini();
        }
    }
};

int main() { D d{}; d.ini(); }

------ EDIT ------

As requested in the comments:

C is a holy class for me. Is it possible to not hack C?

Here is a possible alternative that doesn't require you to modify C:

#include<vector>

struct C {
    void ini() {}
};

struct Wrapper {
    Wrapper(std::vector<C*> &vec) {
        vec.push_back(*this);
        // ...
    }

    operator C *() { return &c; }

private:
    C c;
};

class D{
    std::vector<C*> regis{};

public:
    Wrapper field1{regis};
    Wrapper field2{regis};
    Wrapper field3{regis};

    void ini(){
        for(auto ele:regis){
            ele->ini();
        }
    }
};

int main() { D d{}; d.ini(); }
Velvetvelveteen answered 27/4, 2017 at 5:40 Comment(16)
I have to add field3 to a potentially-far-away line. In other words, std::vector<C*> regis={...} is far from C field3; hence the problem remains.Darondarooge
@Darondarooge Far? You mean visually far? Put it immediately above the other fields if it helps.Velvetvelveteen
Yes, it is visually far. In bad day, I will forget it for sure. It happened.Darondarooge
@Darondarooge Added another solution that probably helps you.Velvetvelveteen
It looks nice. Thank.Darondarooge
@SergeBallesta Well, then why don't you use C field1{regis} instead? This would avoid narrowing too.Velvetvelveteen
C is a holy class for me. Is it possible to not hack C?Darondarooge
@Darondarooge See edit section directly in the answer above.Velvetvelveteen
Thank..... Meaning of field1is now changed. .... It might be improved by make Wrapper derived from C, but it is not the same. ... It is not actually a problem though, but I can hear those fields crying for its original identity.Darondarooge
@Darondarooge Wrapper has operator C *() and you can use it whenever you would have used a C *. You can add also a cast to a reference to C if needed. That being said, you can't have the cake and eat it, so...Velvetvelveteen
@Velvetvelveteen Should not regis be moved above the fields? In your sample the regis is constructed after the fields and should cause a crash: "non-static data members are initialized in order of declaration in the class definition".Minier
@Minier As Long as fields get only its address and don't try to use it, it doesn't matter that much if it has been properly constructed or not. It has an address to take in any case after all.Velvetvelveteen
@Velvetvelveteen Don't you pass regis to the fields, and then the fields call push_back on the uninitialized regis in their constructor? You should move regis above the fields how it is in the original post, and then it is ok.Minier
@Minier Oh, you right, didn't see they push in themselves, sorry. Thank you.Velvetvelveteen
Instead of the operator C *() why not simply inherit from C?Snoop
@Snoop It makes sense, but Wrapper isn't a C. I prefer to keep them separated.Velvetvelveteen
R
3

Maybe you are looking for a dummy fields initialized by lambda capturing this pointer invoked immediately after declaration:

#include <iostream>

struct C { };

class D{
    int field1=5;
    int dummy1{[&]{ std::cout << field1 << std::endl; return 0;}()};
    int field2=7;
    int field3=8;
    int dummy2{[&]{ std::cout << (field1 + field2 + field3) << std::endl; return 0;}()};
};

int main() {
    D d;
}

Output:

5  
20

[live demo]

Ruching answered 19/5, 2017 at 12:2 Comment(2)
I will still waste 1 char, but it is more beautiful than mine. Thank.Darondarooge
@Darondarooge well theoretically to avoid wasting additional memory you could do sth like thisRuching
A
3

I'd turn it around and initialise the vector with the "real" objects of type C, let the members field0 .. field2 be of type "reference to C", i.e. C&, and initialise them with the respective elements of the vector. The advantage of this "turn around" is that the various C-elements are placed one after each other in the vector, which is the most compact way and without padding, while still providing "named" data members (i.e. field0..field2) for accessing the elements without exposing the vector member itself.

The custom "static" code can then be done with a comma expression, which lets you place almost arbitrary code. For code that is not allowed in comma expressions, e.g. the declaration of variables, one could still call member functions or use lambda-expressions. So we do not need any dummy members for "static" code executed between the various fields.

The only place where we needed a dummy member is for code to be executed after the last field_x; so the overhead is a single char-value, and the "static" code here is again solved with a comma expression.

See the following code, which demonstrates the approach. Note that you actually do not have to touch class C; member functions/data C::setX,C::print, and int x have been introduced for demonstration purpose only:

class C{
public:
    void ini(){/* some code */}

    // the following methods and data members are actually not necessary; they have been introduced just for demonstration purpose:
    void setX(int _x) { x = _x;  };
    void print() { cout << x << endl; }
    int x;
};

class D{
protected:
    std::vector<C> regis = std::vector<C>(3); //will ini(); later
public:
    C &field0 = (regis[0].setX(5),printAllRegis("at field0:"),regis[0]);
    C &field1 = (regis[1].setX(7),printAllRegis("at field1:"),regis[1]);
    C &field2 = (regis[2].setX(regis[0].x + regis[1].x),printAllRegis("at field2:"),regis[2]);
    char dummy = (cout << "after field2: ", field2.print(), '0');
    D(){ }

    void ini(){
        for(auto ele:regis){ ele.ini(); }
    }

    void printAllRegis(string header) {
        int i=0;
        cout << header << endl;
        for(auto ele:regis){ cout << "  field" << i++ << ":"; ele.print(); }
    }
};

int main() {

    D d;
    /* Output:
     at field0:
       field0:5
       field1:0
       field2:0
     at field1:
       field0:5
       field1:7
       field2:0
     at field2:
       field0:5
       field1:7
       field2:12
     after field2: 12
     */

    return 0;
}
Airway answered 23/5, 2017 at 21:56 Comment(4)
It is surprising for me that this snippet is compilable. What is the tuple-like parenthesis syntax in C& c=(....,.....,....); called?Darondarooge
It's the Built-In Comma Operator (cf. en.cppreference.com/w/cpp/language/operator_other); Its syntax is E1,E2, where E1 and E2 are expressions, and E1 is evaluated before E2 gets evaluated; The result of (E1,E2) in total is the result of evaluation of E2. And its standardised (for a long time now, I think back to C89/90 or longer).Airway
It is beautiful. Because it is a late answer, it makes me doubt why people didn't suggested it. .... because comma operator is not popular (?).Darondarooge
Right, I think it is not to popular; It's handy if ohne wants some side effecst before a simple expression.Airway
C
2

Looking at this question, it reminds me to my own "Object with properties" concept. I consider it rather as pragmatical than elegant but I dare to present it:

To establish the dependency between Field and Object, I introduced a back-reference to Object in Field. At the cost of an additional member, this has certain conveniencies.

class Field – the base class required to store fields:

class Object; // forward reference

// super class of all object fields
class Field {
  // variables:
  private:
    // parent object
    Object &_obj;
  // methods:
  protected:
    // constructor.
    Field(Object &obj);
    // destructor.
    ~Field();
    // disabled:
    Field(const Field&) = delete;
    Field operator=(const Field&) = delete;
}

The corresponding class Object – the field container:

#include <vector>

// super class of objects which may contain fields
class Object {
  // friends:
  friend class Field;
  // variables:
  private:
    // table of all registered fields
    std::vector<Field*> _pFields;
  // methods:
  protected:
    // constructor.
    Object() = default;
    // destructor.
    virtual ~Object() = default;
    // disabled:
    Object(const Object&) = delete;
    Object& operator=(const Object&) = delete;
};

The implementation of class Field has to "know" both classes:

#include <algorithm>

// implementation of class Field

Field::Field(Object &obj): _obj(obj)
{
  _obj._pFields.push_back(this);
}

Field::~Field()
{
  _obj.erase(
    std::find(
      _obj._pField.begin(), _obj._pField.end(), this));
}

The template for field instances:

// a class template for derived fields
template <typename VALUE>
class FieldT: public Field {
  // variables:
  private:
    // the value
    VALUE _value;
  // methods:
  public:
    // constructor.
    FieldT(Object &obj, const VALUE &value):
      Field(obj), _value(value)
    { }
    // copy constructor.
    FieldT(Object &obj, const FieldT &field):
      Field(obj), _value(field._value)
    { }
    // disabled:
    FieldT(const FieldT&) = delete;
    FieldT& operator=(const FieldT&) = delete;
};

The advantages and disadvantages as considered by me:

Pros:

  1. The object would never register non-fields.

  2. You may never forget to construct fields explicitly because the C++ compiler will remind you in this case. (Fields are not default constructable.)

  3. You may decide in a copy constructor of a derived Object whether to copy the field or initialize it with a constant (or alternative) value instead.

Cons:

  1. It requires explicitly written code in every derived Object which might be considered as tedious. (I, personally, like explicit code to a certain amount to make maintenance easier.)

  2. It is not foolproof. E.g. fields created with new are "begging" for memory leaks. (Although... I never considered to simply delete the operator new(). I've to check out this soon.)

So, how could this look in real-life:

// storage of a 2d vector
struct Vec2f {
  float x, y;
  Vec2f(float x, float y): x(x), y(y) { }
};

// storage of a 2d circle
class Circle: public Object {
  // fields:
  public:
    // center
    FieldT<Vec2f> center;
    // radius
    FieldT<float> radius;
 // methods:
 public:
   // constructor.
   explicit Circle(float x, float y, float r):
     Object(),
     center(*this, Vec2f(x, y)),
     radius(*this, r)
   { }
   // copy constructor.
   Circle(const Circle &circle):
     Object(),
     center(*this, center),
     radius(*this, radius)
  { }
};

Note:

You may wonder why the fields are public – this is intentional. In my real-life incarnation, these fields have a lot more i.e. getter and setter as well as (optional) signal slots to install guardians and modification notifier. My intention was to free me from the tedious writing of access methods in the objects.

In real-life this concept has even more potential:

  • It's sufficient for generic I/O classes which may write and read XML files or other formats.

  • It can be obtained in a second concept of generic GUI classes to simplify the creation of user interfaces in a generic way.

Worth of the costs for administrational extra data:

Some extra bytes seems often enough worth to me if they promise a lot of safed development time. Of course, there are counter examples where I consider every single bit e.g. the vertex data when loading geometry for visual simulation. (For the vertex data example, I never considered a property concept to access every single vertex. Instead, I used the properties to access arrays of vertices as once.)

Concerning the additional requirement of additional construction details:

class D{ int field1=5; { do something very custom; } //^ have to be executed after "field1=5;" but before "field2=7" int field2=7; int field3=8; { do something very custom ; }//have to be executed after "field3=8;" };

I would choose a simple solution which has nothing to do with the above concept:

class D {
  int field1 = 5;
  struct InitBefore {
    InitBefore() {
      // do something very custom;
    }
  } initBefore;
  int field2 = 7;
  int field3 = 8;
  struct InitAfter {
    InitAfter() {
      // do something very custom;
    }
  } initAfter;
  // the rest...
};

Unfortunately, this doesn't fulfill the extra requirement:

without wasting 1 char for each block.

(The reason can be found in SO: sizeof empty structure is 0 in C and 1 in C++ why?.)

May be, it could be solved exploiting the sequence operator and static methods applied to the constructor of the appropriate member. This even seems so ugly to me that I don't dare to sketch code for this.

May be, it could be solved to make an embedded, private derived class of the respective field where the "very custom code" can be added in the constructor. Due to the fact that fields actually have data the empty struct/class problem should be prevented.

Actually, I never cared about the non-zero structs/classes because I needed this trick itself only in rare cases (where only a few resp. instances are expected).

As there are (at the time of writing) still 7 days until the bounty is over I will keep this in mind...

Corundum answered 18/5, 2017 at 7:7 Comment(0)
A
2

something like this?

class D
{
public:
    D()
    {   
        objects.emplace("field1", std::make_unique<C>());
        //{ do something very custom; }
        //^ have to be executed after "field1=5;" but before "field2=7"
        objects.emplace("field2", std::make_unique<C>());
        objects.emplace("field3", std::make_unique<C>());
        //{ do something very custom; }//have to be executed after "field3=8;"

        // if you want you can store the pointers in the vector. but since you store everything in the map you maybe wanna drop the vector 
        for (const auto& o : objects)
        {
            regis.emplace_back(o.second.get());
        }
    }

private:
    void ini()
    {
        for (auto ele : regis){
            ele->ini();
        }

        /* or use the map to iterate 
        for (const auto& o : objects)
        {
            o.second->ini();
        }       
        */

        // to acces a specific field:
        //objects["field1"]->any_obj_from_c;
    }
    std::vector<C*> regis; //will ini(); later
    std::map<std::string, std::unique_ptr<C>> objects;
};
Aargau answered 18/5, 2017 at 13:2 Comment(1)
Very poor performance but kinda creative. I will certainly not use it. Thank.Darondarooge
W
1

This may be an overkill here, but alternative still: instead of maintaining D.h and D.cpp write small app, that will generate D.h and D.cpp on base of D.txt, and maintain D.txt. So when you will need to add a new field to D (or some other change), you just add a line to D.txt and run generator.

Whiting answered 24/5, 2017 at 15:16 Comment(0)
U
1

I think it might be smarter to create this the other way around. Instead of artificially enhancing your structure with an additional vector of pointers for iteration purposes, enhance a vector with named fields.

A strong indicator for why you would want to do this is that the layout and naming scheme of your fields already sounds exactly like an array. Think about it, your names are just covering up that you most likely access consecutive fields with indices.

class C {
public:
    int value; // For code example
    void ini() { value = 42; };
};

class D {
    std::array<C, 4 /*or w/e you need*/ > storage;
public:
    // Reference initializers are compulsory, so less chance of forgetting & bugs
    int field_index = 0;
    C& field1 = storage.at(field_index++);
    C& field2 = storage.at(field_index++);
    void ini(){
        for(auto& ele: storage) {
            ele.ini();
        }
    }
    // We can even do some templating due to std::array
    template<size_t I> C& field() { 
        return std::get<I>(storage); 
    }
};

Now you can add additional stored Cs by increasing the size count of the array, and name any field by simply adding an additional member line. Of course you could also store the array as a member instead of inheriting from it in case you need the member overloads or want a flat hierarchy.

Additional plus: Since all access indices are constant expression, any good compiler might inline the actual access, no matter if done by index or by field.

int main() {
     D d; d.ini();
     int s = d.field1.value + d.field2.value;
     // alternatively d.field<0>().value + d.field<1>().value;
     printf("%i", s); // Compiler can deduce to printf("%i", 84);
}

For example the above will compile to basically little more than printf("%i", 84) with both clang and gcc. You can check this with your own compiler or see the link to https://godbolt.org/g/50VjyY.


Edit: I realized that maybe initialization semantics are the initial reasoning for listing multiple consecutive fields. But any initialization you would normally do on the fields directly can arguably also be done in the array initializer. Assume some constructor

// Replace this (from your structure)
D::D(/*some arguments*/) : field1(args), field2(otherargs) {}

// with this
D::D(/*same arguments*/) : storage{C{args}, C{otherargs}} {}

Edit 2: Although it might not accurately answer the fundamental problem of how to nicely express the above data representation, it should be noted that there is indeed a way to execute arbitrary code in the way you might have originally intended it to but you have to very careful.

class D{
    std::vector<C*> regis;                        
public:
    C field1 = (regis.push_back(&field1), C{});
    C field2 = (regis.push_back(&field2), C{});
    C field3 = (regis.push_back(&field3), C{});
    D(){    }  //<-- empty
    void ini(){
        for(auto ele:regis){
            ele->ini();
        }
    }
};

Here you need to acknowledge than any initialization in the initializer list of any other constructor you might add later will remove the push_back of the respective pointer! This will likely introduce an hard to find bug and cause obscure side effect. For this reason I would never advise for using the comma operator , in default member initializers.

By using the standard correctly, you can theoretically use values of previous members

12.6.2.5

Initialization shall proceed in the following order:

...

[...] nonstatic data members shall be initialized in the order they were declared in the class definition.

Note that this will also apply to the other initialization with array as well and is not exclusive to your original approach.

Unseemly answered 25/5, 2017 at 2:43 Comment(5)
Thank. ..... I am trying to check my understanding .... The number 1 inside C& field2 = storage.at(1); AND the number 4 at std::array<C, 4> storage; -> have to be counted by coders manually. .... It is probably a disadvantage.Darondarooge
The size of the array is, imho, not that different to counting the number of members and I think. The manual index counting can be remedied by introducing a counter variable index and indexing with index++ instead of 1 or 2 directly. It should probably be noted that the memory (and performance) might be vastly superior to the vector implementation since the data layout is much clearer.Unseemly
I hope the second edit includes information more directly applicable to your immediate intent. Use it carefully and be aware of potentially drastic side-effects of small changes.Unseemly
I agree index++ is a good idea. .... It is a good reference! Unfortunately, Stephan Lechner has already mentioned about (....,....) a little bit before you. Thank.Darondarooge
I added a templating approach, which is only possible with std::array.Unseemly

© 2022 - 2024 — McMap. All rights reserved.