3

In my implementations, I should use a long array but my problem with array is that its indices do not much make sense to me. Instead I would like to use hierarchal classes. However, sometimes I need to treat them in a bulk way such as when calculating differences and derivatives or averages.

All members are double and seems aligning does not make any problem. Here is an example as follows. This example apparently works fine.

My question is that is this structure of programming prone to failure on different compilers or systems?

#include <iostream>

class Room
{
public:
    double size;
    double temperature;
    double humidity;
    double oxigen_level;
    // etc
};

class Kitchen
{
public:
    double fan_speed;
    double temperature;
};

class Building // a hierarchal class
{
public:
    Room rooms[5];
    double distance;
    Kitchen kitchen;
};

Building diff(
    const Building &b1,
    const Building &b2) // treat as an array
{
    Building r=b2;
    double *p1=(double *)&b1;
    double *pr=(double *)&r;
    for(int i=0;i*sizeof(double)<sizeof(Building);i++)
        pr[i]-=p1[i];
    return r;
}

int main()
{
    Building b1,b2,delta;
    b1.rooms[3].humidity=0.44;
    b2.rooms[3].humidity=0.43;
    delta=diff(b1,b2);
    std::cout
        <<"diff: "
        <<delta.rooms[3].humidity
        <<std::endl;
    return 0;
}
ar2015
  • 5,558
  • 8
  • 53
  • 110
  • 2
    Say welcome to **real** arrays: [std::vector](http://en.cppreference.com/w/cpp/container/vector)/[std::array](http://en.cppreference.com/w/cpp/container/array). – O'Neil Jan 10 '18 at 04:02
  • @O'Neil, They will have the same problem of classical arrays. They use indices instead of hierarchies. std::vector has slight overhead. The classes will be used in an intensive computation. – ar2015 Jan 10 '18 at 04:34
  • 1
    `double *p1=(double *)&b1` exhibits undefined behavior (though chances are high you'd get away with it). Or to be precise, the subsequent use of `p1` does. – Igor Tandetnik Jan 10 '18 at 05:43
  • @IgorTandetnik, Would you please explain how this is undefined behavior? – ar2015 Jan 10 '18 at 06:19
  • 1
    Related: [Does pointer arithmetic have uses outside of arrays?](https://stackoverflow.com/questions/7576948/does-pointer-arithmetic-have-uses-outside-of-arrays), [Is a pointer with the right address and type still always a valid pointer since C++17?](https://stackoverflow.com/questions/48062346/is-a-pointer-with-the-right-address-and-type-still-always-a-valid-pointer-since) – xskxzr Jan 10 '18 at 07:47
  • @xskxzr, many thanks. Would you please provide reasons as well? – ar2015 Jan 10 '18 at 07:50
  • Reasons for what? – xskxzr Jan 10 '18 at 07:50
  • They just mention that I should not do that. But I look for a reason about why I shouldn't? – ar2015 Jan 10 '18 at 07:51
  • 1
    Because generally you have a desired behaviour of your program. If you program has undefined behaviour, you can't be sure the behaviour of your program matches your desired behaviour. It doesn't have to be deterministic – Caleth Jan 10 '18 at 07:58
  • @Caleth, Would you please explain how the program is undefined behavior? – ar2015 Jan 10 '18 at 08:02
  • Because the standard says so. C++ is defined as an abstract machine, where many syntactic constructs have no defined meaning in that abstract machine. The standard *explicitly permits* implementations to implement the behaviour of this abstract machine in any way they want – Caleth Jan 10 '18 at 09:44
  • 3
    "[**\[basic.lval\]/8**](http://eel.is/c++draft/basic.lval#11) If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined..." (followed by a bunch of cases none of which applies to `Building` / `double` pair). – Igor Tandetnik Jan 10 '18 at 13:36
  • @IgorTandetnik, So it seems, in my case, this is fine. Would you please convert your comment to an answer? – ar2015 Jan 11 '18 at 00:45
  • 1
    Well, I definitely meant to suggest with my comment that this is **not** fine. I'm not sure how you managed to read the opposite meaning into it. – Igor Tandetnik Jan 11 '18 at 01:03
  • @IgorTandetnik, sorry I missed the part `other than`. – ar2015 Jan 11 '18 at 02:18
  • Answer updated. Added an example in compiler explorer. – Robert Andrzejuk Jan 11 '18 at 05:58
  • Added link to benchmark in answer. Optimized code by compiler runs faster than optimizations by hand. – Robert Andrzejuk Jan 12 '18 at 10:27

1 Answers1

2

What you are doing in diff is a nightmare. If you going to do casts like this, it's better to stick with a plain array for your variables.

But I would use your thought of using the structures to help you with your calculations.

Make the classes as wrappers for your array. Then instead of variables, let them be functions ( double size() {... }). Use those functions in your calculations.

As usual always measure before prematurely optimizing.


Edit:

It is a nightmare, because types are built up, than the compiler is cheated into doing something else. The "assumed" underlying structure is used, when it dosesn't have to be as someone would expect it.

Here is a version I would make.

Is it better? It has less assembler instructions(75 vs 86), than the main example. And it has the intended logic visible to the reader. It is easy to debug. ...

The two examples would have to be benchmarked. But I don't think there is much of a diffrence.

EDIT: Actually there is a difference of speed. The below code runs faster on GCC, Clang and MSVC than the code in the main example.

Quick Bench Benchmark

Compiler Explorer example

#include <iostream>

class Room
{
public:
    double size{};
    double temperature{};
    double humidity{};
    double oxigen_level{};
    // etc
    Room& operator-=( const Room& r )
    {
        size -= r.size;
        temperature -= r.temperature;
        humidity -= r.humidity;
        oxigen_level -= r.oxigen_level;

        return *this;
    }
};

class Kitchen
{
public:
    double fan_speed{};
    double temperature{};

    Kitchen& operator-=( const Kitchen& k )
    {
        fan_speed -= k.fan_speed;
        temperature -= k.temperature;

        return *this;
    }
};

class Building // a hierarchal class
{
public:
    static const int room_count{5};

    Room    rooms[ room_count ];
    double  distance{};
    Kitchen kitchen;

    Building operator-( const Building& b )
    {
        Building ret = *this;

        for ( int i = 0; i < room_count; i++ )
            ret.rooms[ i ] -= b.rooms[ i ];

        ret.distance -= b.distance;
        ret.kitchen -= b.kitchen;

        return ret;
    }
};    
int main()
{
    Building b1,b2,delta;
    b1.rooms[3].humidity=0.44;
    b2.rooms[3].humidity=0.43;
    delta=b1-b2;//diff(b1,b2);
    std::cout
        <<"diff: "
        <<delta.rooms[3].humidity
        <<std::endl;
    return 0;
}
Robert Andrzejuk
  • 5,076
  • 2
  • 22
  • 31
  • Why this is a nightmare? All members are supposed to be `double` and we accept it as a fact. The classes are supposed to be created automatically and not by a programmer so there is not chance of making mistake if that's what you mean. Having no overhead is very important for me as the program must be as fast as possible. By using functions, if you mean something like [this](https://stackoverflow.com/questions/45540260/), I have tried this approach before and it will be a messy headache when class gets large. Out of control! – ar2015 Jan 10 '18 at 06:16
  • I don't think it has anything to do with the pointer. But I do think the optimization has been reduced. – Robert Andrzejuk Jan 11 '18 at 06:12
  • Actually I would say it differently : with this code the optimizer finds more ways to optimize it. – Robert Andrzejuk Jan 11 '18 at 06:30
  • This is a good design except for verbose operators which must be updated every time with the class. But at least this follows standards. – ar2015 Jan 11 '18 at 06:52
  • @ar2015 why would you *not have to* change where data is used if you change what the structure of the data is? In your previous design, you have to *find* all the places where the data is used together – Caleth Jan 12 '18 at 10:39
  • @Caleth, then the class will look crowded. – ar2015 Jan 12 '18 at 11:03
  • Thanks for the benchmark. How many times is it called? Is there any plain english explanation for why the second design is faster? – ar2015 Jan 12 '18 at 11:04
  • On my laptop Before test is run 11'000'000 times and the After is run 22'000'000 times. The explaination requires an intense analysis. – Robert Andrzejuk Jan 12 '18 at 11:10
  • @ar2015 So? That's a *positive benefit*. It is also normal for class definitions in a header to only have function declarations, with the function definitions in a separate compilation unit. Many operators can be implemented in terms of a related operator, e.g. you only need *one* of `<`, `<=`, `>`, `>=` with `using std::rel_ops` to have all of them. unary `-` and `+=` combine to make binary `-`, binary `+`, `-=` – Caleth Jan 12 '18 at 11:23
  • @each time a field is added, the operator must be updated as well. Still can be tolerated but not preferred. – ar2015 Jan 12 '18 at 11:31