4

Note: This is a complete re-wording of a question I posted a while ago. If you find they are duplicate, please close the other one.

My problem is quite general but it seems that it could be explained more easily based on a concrete simple example. So imagine I want to simulate the electricity consumption in an office throught time. Let's assume that there is only a light and heating.

class Simulation {
    public:
        Simulation(Time const& t, double lightMaxPower, double heatingMaxPower)
            : time(t)
            , light(&time,lightMaxPower) 
            , heating(&time,heatingMaxPower) {}

    private:
        Time time; // Note : stack-allocated
        Light light;
        Heating heating;
};

class Light {
    public:
        Light(Time const* time, double lightMaxPower)
            : timePtr(time)
            , lightMaxPower(lightMaxPower) {}

        bool isOn() const {
            if (timePtr->isNight()) {
                return true;
            } else {
                return false;
            }
        }
        double power() const {
            if (isOn()) {
                return lightMaxPower;
            } else {
                return 0.;
            }
    private:
        Time const* timePtr; // Note : non-owning pointer
        double lightMaxPower;
};

// Same kind of stuff for Heating

The important points are:

1.Time cannot be moved to be a data member Light or Heating since its change does not come from any of these classes.

2.Time does not have to be explicitly passed as a parameter to Light. Indeed, there could be a reference to Light in any part of the program that does not want to provide Time as a parameter.

class SimulationBuilder {
    public:
        Simulation build() {
            Time time("2015/01/01-12:34:56");
            double lightMaxPower = 42.;
            double heatingMaxPower = 43.;
            return Simulation(time,lightMaxPower,heatingMaxPower);
        }
};

int main() {
    SimulationBuilder builder;
    auto simulation = builder.build();

    WeaklyRelatedPartOfTheProgram lightConsumptionReport;

    lightConsumptionReport.editReport((simulation.getLight())); // No need to supply Time information 

    return 0;
}

Now, Simulation is perfectly find as long as it is not copy/move constructed. Because if it is, Light will also get copy/move constructed and by default, the pointer to Time will be pointing to the Time in the old Simulation instance which is copied/moved from. However, Simulation actually is copy/move constructed in between the return statement in SimulationBuilder::build() and the object creation in main()

Now there are a number of ways to solve the problem:

1: Rely on copy elision. In this case (and in my real code) copy elision seems to be allowed by the standard. But not required, and as a matter of fact, it is not elided by clang -O3. To be more precise, clang elides Simulation copy, but does call the move ctor for Light. Also notice that relying on an implementation-dependent time is not robust.

2: Define a move-ctor in Simulation:

Simulation::Simulation(Simulation&& old) 
    : time(old.time)
    , light(old.light)
    , heating(old.heating)
{
    light.resetTimePtr(&time);
    heating.resetTimePtr(&time);
}

Light::resetTimePtr(Time const* t) {
    timePtr = t;
}

This does work but the big problem here is that it weakens encapsulation: now Simulation has to know that Light needs more info during a move. In this simplified example, this is not too bad, but imagine timePtr is not directly in Light but in one of its sub-sub-sub-member. Then I would have to write

Simulation::Simulation(Simulation&& old) 
    : time(old.time)
    , subStruct(old.subStruct)
{
    subStruct.getSubMember().getSubMember().getSubMember().resetTimePtr(&time);
}

which completly breaks encapsulation and the law of Demeter. Even when delegating functions I find it horrible.

3: Use some kind of observer pattern where Time is being observed by Light and sends a message when it is copy/move constructed so that Light change its pointer when receiving the message. I must confess I am lazy to write a complete example of it but I think it will be so heavy I am not sure the added complexity worth it.

4: Use a owning pointer in Simulation:

class Simulation {
    private:
        std::unique_ptr<Time> const time; // Note : heap-allocated
};

Now when Simulation is moved, the Time memory is not, so the pointer in Light is not invalidated. Actually this is what almost every other object-oriented language does since all objects are created on the heap. For now, I favor this solution, but still think it is not perfect: heap-allocation could by slow, but more importantly it simply does not seems idiomatic. I've heard B. Stroustrup say that you should not use a pointer when not needed and needed meant more or less polymorphic.

5: Construct Simulation in-place, without it being return by SimulationBuilder (Then copy/move ctor/assignment in Simulation can then all be deleted). For instance

class Simulation {
    public:
        Simulation(SimulationBuilder const& builder) {
            builder.build(*this);
        }

    private:
        Time time; // Note : stack-allocated
        Light light;
        Heating heating;
        ...
};



class SimulationBuilder {
    public:
        void build(Simulation& simulation) {

            simulation.time("2015/01/01-12:34:56");
            simulation.lightMaxPower = 42.;
            simulation.heatingMaxPower = 43.;
    }
};

Now my questions are the following:

1: What solution would you use? Do you think of another one?

2: Do you think there is something wrong in the original design? What would you do to fix it?

3: Did you ever came across this kind of pattern? I find it rather common throughout my code. Generally though, this is not a problem since Time is indeed polymorphic and hence heap-allocated.

4: Coming back to the root of the problem, which is "There is no need to move, I only want an unmovable object to be created in-place, but the compiler won't allow me to do so" why is there no simple solution in C++ and is there a solution in another language ?

Community
  • 1
  • 1
Bérenger
  • 2,678
  • 2
  • 21
  • 42

4 Answers4

2

If all classes need access to the same const (and therefore immutable) feature, you have (at least) 2 options to make the code clean and maintainable:

  1. store copies of the SharedFeature rather than references - this is reasonable if SharedFeature is both small and stateless.

  2. store a std::shared_ptr<const SharedFeature> rather than a reference to const - this works in all cases, with almost no additional expense. std::shared_ptr is of course fully move-aware.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Unfortunatly, SharedFeature is variable during run-time (even if the reference in `MySubStruct` is const). Your solution with shared pointers is similar to my solution #4: not bad but still not completely satisfying because there is no need for heap-allocation. – Bérenger Apr 02 '15 at 23:18
  • If you construct the shared_ptr with a no-op deleter it will happily accommodate a stack-allocated object. See the 2-argument constructors. – Richard Hodges Apr 03 '15 at 07:34
  • Not sure I understand. So I keep a regular `SharedFeature` in `MyClass`, and create `std::shared_ptr` in `MySubStruct`, `MyOtherSubStruct` with a pointer to `feature` in `MyClass` and no-op deleter? But then when `MyClass` is moved, `feature` is also moved and I don't see how the shared_ptrs can *know* its new adress. – Bérenger Apr 03 '15 at 16:35
  • I think to go further we need a discussion on what your really trying to achieve. There might be an easier way. – Richard Hodges Apr 03 '15 at 16:51
  • If you are still interested, I reworded the question with a more concrete example – Bérenger Apr 07 '15 at 22:56
1

EDIT: Due to the class naming and ordering I completely missed the fact that your two classes are unrelated.

It's really hard to help you with such an abstract concept as "feature" but I'm going to completely change my thought here. I would suggest moving the feature's ownership into MySubStruct. Now copying and moving will work fine because only MySubStruct knows about it and is able to make the correct copy. Now MyClass needs to be able to operate on feature. So, where needed just add delegation to MySubStruct: subStruct.do_something_with_feature(params);.

If your feature needs data members from both sub struct AND MyClass then I think you split responsibilities incorrectly and need to reconsider all the way back to the split of MyClass and MySubStruct.

Original answer based on the assumption that MySubStruct was a child of MyClass:

I believe the correct answer is to remove featurePtr from the child and provide a proper protected interface to feature in the parent (note: I really do mean an abstract interface here, not just a get_feature() function). Then the parent doesn't have to know about children and the child can operator on the feature as needed.

To be completely clear: MySubStruct will not know that the parent class even HAS a member called feature. For example, perhaps something like this:

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • To make sure I understand what you mean: each time I operate with `feature` in `MySubStruct`; instead of `MySubStruct.do()` called from `MyClass` with `feature` refered to as a data member, I call `MySubStruct.do(feature)` by passing it as an argument ? – Bérenger Apr 02 '15 at 17:06
  • Ok I understand but the problem is that `feature` cannot be a member of `MySubStruct` because it is also pointed to by, say, `MySubStruct2`, `MySubStruct3` (see the sentence after first block of code) – Bérenger Apr 02 '15 at 18:27
  • I added a comment to make clear that MySubStruct has no inheritance relationship with MyClass – Bérenger Apr 02 '15 at 18:28
  • I added a concrete example at the end – Bérenger Apr 02 '15 at 18:40
1

1: What solution would you use? Do you think of another one?

Why not apply a few design patterns? I see uses for a factory and a singleton in your solution. There are probably a few others that we could claim work but I am way more experienced with applying a Factory during a simulation than anything else.

  • Simulation turns into a Singleton.

The build() function in SimulationBuilder gets moved into Simulation. The constructor for Simulation gets privatized, and your main call becomes Simulation * builder = Simulation::build();. Simulation also gets a new variable static Simulation * _Instance;, and we make a few changes to Simulation::build()

class Simulation
{
public:
static Simulation * build()
{
    // Note: If you don't need a singleton, just create and return a pointer here.
    if(_Instance == nullptr)
    {
        Time time("2015/01/01-12:34:56");
        double lightMaxPower = 42.;
        double heatingMaxPower = 43.;
        _Instance = new Simulation(time, lightMaxPower, heatingMaxPower);
    }

    return _Instance;
}
private:
    static Simulation * _Instance;
}

Simulation * Simulation::_Instance = nullptr;
  • Light and Heating objects get provided as a Factory.

This thought is worthless if you are only going to have 2 objects inside of simulation. But, if you are going to be managing 1...N objects and multiple types, then I would strongly encourage you utilize a factory, and a dynamic list (vector, deque, etc.). You would need to make Light and Heating inherit from a common template, set things up to register those classes with the factory, set the factory so that it is templated and an instance of the factory can only create objects of a specific template, and initialize the factory for the Simulation object. Basically the factory would look something like this

template<class T>
class Factory
{
    // I made this a singleton so you don't have to worry about 
    // which instance of the factory creates which product.
    static std::shared_ptr<Factory<T>> _Instance;
    // This map just needs a key, and a pointer to a constructor function.
    std::map<std::string, std::function< T * (void)>> m_Objects;

public:
    ~Factory() {}

    static std::shared_ptr<Factory<T>> CreateFactory()
    {
        // Hey create a singleton factory here. Good Luck.
        return _Instance;
    }

    // This will register a predefined function definition.
    template<typename P>
    void Register(std::string name)
    {
        m_Objects[name] = [](void) -> P * return new P(); };
    }

    // This could be tweaked to register an unknown function definition,
    void Register(std::string name, std::function<T * (void)> constructor)
    {
        m_Objects[name] = constructor;
    }

    std::shared_ptr<T> GetProduct(std::string name)
    {            
        auto it = m_Objects.find(name);
        if(it != m_Objects.end())
        {
            return std::shared_ptr<T>(it->second());
        }

        return nullptr;
    }
}

// We need to instantiate the singleton instance for this type.
template<class T>
std::shared_ptr<Factory<T>> Factory<T>::_Instance = nullptr;

That may seem a bit weird, but it really makes creating templated objects fun. You can register them by doing this:

// To load a product we would call it like this:
pFactory.get()->Register<Light>("Light");
pFactory.get()->Register<Heating>("Heating");

And then when you need to actually get an object all you need is:

std::shared_ptr<Light> light = pFactory.get()->GetProduct("Light");

2: Do you think there is something wrong in the original design? What would you do to fix it?

Yeah I certainly do, but unfortunately I don't have much to expound upon from my answer to item 1.

If I were to fix anything I start "fixing" by seeing what a Profiling session tells me. If I was worried about things like time to allocate memory, then profiling is the best way to get an accurate idea about how long to expect allocations to take. All the theories in the world cannot make up for profiling when you are not reusing known profiled implementations.

Also, if I were truly worried about the speed of things like memory allocation then I would take into consideration things from my profiling run such as the number of times that an object is created vs the objects time of life, hopefully my profiling session told me this. An object like your Simulation class should be created at most 1 time for a given simulation run while an object like Light might be created 0..N times during the run. So, I would focus on how creating Light objects affected my performance.


3: Did you ever came across this kind of pattern? I find it rather common throughout my code. Generally though, this is not a problem since Time is indeed polymorphic and hence heap-allocated.

I do not typically see simulation objects maintain a way to see the current state change variables such as Time. I typically see an object maintain its state, and only update when a time change occurs through a function such as SetState(Time & t){...}. If you think about it, that kind of makes sense. A simulation is a way to see the change of objects given a particular parameter(s), and the parameter should not be required for the object to report its state. Thus, an object should only update by single function and maintain its state between function calls.

// This little snippet is to give you an example of how update the state.
// I guess you could also do a publish subscribe for the SetState function.
class Light
{
public:
    Light(double maxPower) 
        : currPower(0.0)
        , maxPower(maxPower)
    {}

    void SetState(const Time & t)
    {
        currentPower = t.isNight() ? maxPower : 0.0;
    }

    double GetCurrentPower() const
    {
        return currentPower;
    }
private:
    double currentPower;
    double maxPower;
}

Keeping an object from performing its own check on Time helps alleviate multithreaded stresses such as "How do I handle the case where the time changes and invalidates my on/off state after I read the time, but before I returned my state?"


4: Coming back to the root of the problem, which is "There is no need to move, I only want an unmovable object to be created in-place, but the compiler won't allow me to do so" why is there no simple solution in C++ and is there a solution in another language ?

If you only ever want 1 object to be created you can use the Singleton Design Pattern. When correctly implemented a Singleton is guaranteed to only ever make 1 instance of an object, even in a multithreaded scenario.

v4n3ck
  • 51
  • 3
  • Thank you for this detailed answer. 1: A global object (safely handled though a singleton) indeed solves the problem. But it works if I only have one `Simulation`, which is not the case. The `Factory` is not really adapted to the situation in the sense that `Light` and `Heating` do not share a complete common interface. In the end, I find the code more complicated than my proposition number 4 with no significant advantage. – Bérenger Apr 09 '15 at 16:00
  • Your discussion in 3: is interesting and can be further solved by the Observer pattern I mentionned in proposition 3: Time has a list of objects observing it and when its state changes, it calls observingObject.SetState(*this). – Bérenger Apr 09 '15 at 16:00
  • (I think it is actually what you say with publish/suscribe) – Bérenger Apr 09 '15 at 16:02
  • Right, having more than 1 singleton running per thread is a bit problematic. I don't use singletons, unless I need to guarantee that I only use 1 instance. But like I meant to point out, `build` could always return a pointer or better a shared_ptr to an object. This solves the problem of having a copy occur, and there's no worry about of invalidating your data. Observer is definitely applicable. I tend to forget that it exists. Really though you want to shy away from classes having to much knowledge of other classes. Having `Time` within Light/Heating is on the border of to much knowledge. – v4n3ck Apr 09 '15 at 17:33
  • So you do not see a pointer to a non-polymorphic object as a kind of a waste? Even before talking about performance, I find it as more of a kludge allowing to move an object, than a real clean solution to the problem – Bérenger Apr 09 '15 at 18:56
  • And for the observer pattern, `Time` does not need to know `Light/Heating` but only an interface `TimeObserver` that `Light/Heating` will implement. – Bérenger Apr 09 '15 at 18:58
  • I am confused by your comments. What is your understanding about using pointers? Specifically why would using a pointer be a waste? I use a lot of pointers as a way of reducing memory instantiation in my programs, or when they give me better performance. Also, I strive for clean solutions by following the DRY principle, and only allowing an object that owns a variable to control the var's state. To me, kludgy code exists with globals, and chaining like with your `subStruct.getSubMember().getSubMember().getSubMember().resetTimePtr(&time);` example. – v4n3ck Apr 10 '15 at 16:26
  • Using non-owning pointers is fine. Using owning pointers (i.e. created by new, or with unique_ptr, shared_ptr) is not needed "most of the time". I will send a link where Bjarne Stroustrup himself tells that. From my understanding "most of the time" means "except for polymorphic objects". And in theory, you truly do not need to allocate heap memory, except for objects which you do not know the size at compile-time. And these objects are variable length arrays and polymorphic objects, and basically nothing else. – Bérenger Apr 10 '15 at 16:42
  • 1
    So I found this answer to a question about pointer ownership which kind of seems like what you're talking about: http://programmers.stackexchange.com/a/133303/158369, and it made me realize that I mentally treat pointers as if they are unique pointers at all times. This alleviates problems my code would have in deciding whose responsibility it is to clean the pointer up, and when it should be cleaned up. Also, if I am caring about the stack vs heap I am actually focusing on real time responsiveness and I make my decision based upon what gives me the best response across processes and threads. – v4n3ck Apr 10 '15 at 19:30
  • 1
    Yes, this is the link I was taking about. I tend to follow strict rules for ownership: no pointer unless polymorphic behavior, and each pointer should be owned by one object, hence the use of unique pointers. In my domain, I never found myself in a situation where ownership needed to be shared: until now, it was always clear which object owns and which object simply has a non-owning reference. Including for this `Simulation` example – Bérenger Apr 10 '15 at 20:00
  • Also see http://stackoverflow.com/questions/8706192/which-kind-of-pointer-do-i-use-when – Bérenger Apr 10 '15 at 20:04
1

In the comment to your second solution, you're saying that it weakens the encapsulation, because the Simulation has to know that Light needs more information during move. I think it is the other way around. The Light needs to know that is being used in a context where the provided reference to the Time object may become invalid during Light's lifetime. Which is not good, because it forces a design of Light based on how it is being used, not based on what it should do.

Passing a reference between two objects creates (or should create) a contract between them. When passing a reference to a function, that reference should be valid until the function being called returns. When passing a reference to an object constructor, that reference should be valid throughout the lifetime of the constructed object. The object passing a reference is responsible for its validity. If you don't follow this, you may create very hard to trace relationships between the user of the reference and an entity maintaining lifetime of the referenced object. In your example, the Simulation is unable to uphold the contract between it and the Light object it creates when it is moved. Since the lifetime of the Light object is tightly coupled to the lifetime of the Simulation object, there are 3 ways to resolve this:

1) your solution number 2)

2) pass a reference to the Time object to constructor of the Simulation. If you assume the contract between the Simulation and the outer entity passing the reference is reliable, so will be the contract between Simulation and Light. You may, however, consider the Time object to be internal detail of the Simulation object and thus you would break encapsulation.

3) make the Simulation unmovable. Since C++(11/14) does not have any "in-place constructor methods" (don't know how good a term that is), you cannot create an in-place object by returning it from some function. Copy/Move-elision is currently an optimalization, not a feature. For this, you can either use your solution 5) or use lambdas, like this:

class SimulationBuilder {
    public:
        template< typename SimOp >
        void withNewSimulation(const SimOp& simOp) {
            Time time("2015/01/01-12:34:56");
            double lightMaxPower = 42.;
            double heatingMaxPower = 43.;
            Simulation simulation(time,lightMaxPower,heatingMaxPower);
            simOp( simulation );
        }
};

int main() {
    SimulationBuilder builder;

    builder.withNewSimulation([] (Simulation& simulation) {

        WeaklyRelatedPartOfTheProgram lightConsumptionReport;

        lightConsumptionReport.editReport((simulation.getLight())); // No need to supply Time information
    } 

    return 0;
}

If none fits your needs, then you either have to reevaluate your needs (might be a good option, too) or use heap allocation and pointers somewhere.

Dalibor Frivaldsky
  • 810
  • 1
  • 6
  • 14
  • You are right indeed: there is an implicit contract between `Simulation` and `Light` that is not fulfilled by `Simulation` upon moving. It would be great if I can enforce this contract, but I see no easy way to do it. Using proposition 2) only ensures that the contact is not related upon a move, but does not ensures that it will always be enforced for any operation. – Bérenger Apr 10 '15 at 15:07
  • Also I think your solution 2) only moves the problem in the class containing `Simulation`. Suppose that `Simulation` has now only a ref to `Time` and that both `Simulation` and `Time` are members of `SimulationContainer`. Then `Simulation` has no more problem, but `SimulationContainer` does. – Bérenger Apr 10 '15 at 15:11
  • Yes, that is a correct observation. Solution 2) only moves the problem one level above. However, based on the situation, it might be easier to resolve the problem there. – Dalibor Frivaldsky Apr 10 '15 at 15:32
  • Not in my case. `Simulation` is my top-level structure. It contains basically all the input data structures and relations between them that I need to run a simulation. You can see it as an oriented acyclic graph. If I move the graph, some relations (i.e. pointers to stack-allocated object) are lost since they still point to the previous locations. The funny thing is that I don't really want to move the graph, but do it anyway when using`SimulationBuilder::build` – Bérenger Apr 10 '15 at 16:28
  • I guess the more abstract problem would be: how to safely build/move a graph while keeping all the relations (i.e. pointers) valid? If the pointers point to heap allocated objects, nothing has to be done ; while if they point to stack-allocated-objects, there is no real clean way to do it – Bérenger Apr 10 '15 at 16:31
  • FYI the case of pointers to stack-allocated object happens 3 times in ~20000 lines of code – Bérenger Apr 10 '15 at 16:32
  • That can't be done, regardless of language. Since the pointers are part of the graph, you can either: 1) update the pointers after graph move 2) split the graph into movable/unmovable part so that pointers point only to the unmovable part (heap allocated memory being the unmovable part - just a note, "unmovable" not being used in the C++ meaning) 3) rebuild the graph from scratch at the new location – Dalibor Frivaldsky Apr 10 '15 at 16:44
  • or... not move the graph :) – Dalibor Frivaldsky Apr 10 '15 at 16:47
  • I also think so. I really do not want to move the graph, but I am almost forced to do it by the language. According to you, does this "master input graph-like structure" is OK from a design point of view or is there any alternative ? – Bérenger Apr 10 '15 at 16:51
  • I don't see any fundamental design problem. If you do not really want to move the graph, then just don't. Use either the in-place builder (your solution 5) or the lambda version I provided, whichever you feel more comfortable with. – Dalibor Frivaldsky Apr 10 '15 at 17:26
  • Thanks. I think I will indeed use an in-place builder. It is not as clean as I would like, but it is acceptable. It would be interesting if the C++ standard allowed and enforced in-place construction provided that copy-elision is possible and move/copy constructor/assignment is deleted. – Bérenger Apr 10 '15 at 18:03